Blog. Just Blog

Работа с регулярными выражениями на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Работа с регулярными выражениями на Ассемблере
Работа с регулярными выражениями на Ассемблере

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

В системе за регулярки отвечают инструменты, предоставляемые VBScript Regular Expression. Они доступны в системах, в которых есть Internet Explorer 5.5 и выше, то есть любая версия после Windows 98. У нас сегодня работа с COM, поэтому традиционно начинаем с описания GUID, интерфейсов и констант, которых нет в стандартном пакете FASM.
  1. ; GUID {3F4DACB0-160D-11D2-A8E9-00104B365C9F}
  2. IID_IRegExp2 \
  3.     dd 03F4DACB0h
  4.     dw 0160Dh
  5.     dw 011D2h
  6.     db 0A8h, 0E9h, 000h, 010h, 04Bh, 036h, 05Ch, 09Fh
  7.  
  8. ; GUID {3F4DACA4-160D-11D2-A8E9-00104B365C9F}
  9. CLSID_IRegExp \
  10.     dd 03F4DACA4h
  11.     dw 0160Dh
  12.     dw 011D2h
  13.     db 0A8h, 0E9h, 000h, 010h, 04Bh, 036h, 05Ch, 09Fh
  14.  
  15. ; IID_IRegExp2 Interface
  16. struct IRegExp2
  17.     QueryInterface   dd ?
  18.     AddRef           dd ?
  19.     Release          dd ?
  20.  
  21.     ; IDispatch
  22.     GetTypeInfoCount dd ?
  23.     GetTypeInfo      dd ?
  24.     GetIDsOfNames    dd ?
  25.     _Invoke          dd ?
  26.  
  27.     ; IRegExp2
  28.     get_Pattern      dd ?
  29.     set_Pattern      dd ?
  30.     get_IgnoreCase   dd ?
  31.     set_IgnoreCase   dd ?
  32.     get_Global       dd ?
  33.     set_Global       dd ?
  34.     get_Multiline    dd ?
  35.     set_Multiline    dd ?
  36.     Execute          dd ?
  37.     _Test            dd ?
  38.     Replace          dd ?
  39. ends
  40.  
  41. ; IID_IMatchCollection2 Interface
  42. struct IMatchCollection2
  43.     QueryInterface   dd ?
  44.     AddRef           dd ?
  45.     Release          dd ?
  46.  
  47.     ; IDispatch
  48.     GetTypeInfoCount dd ?
  49.     GetTypeInfo      dd ?
  50.     GetIDsOfNames    dd ?
  51.     _Invoke          dd ?
  52.  
  53.     ; IMatchCollection2
  54.     get_Item         dd ?
  55.     get_Count        dd ?
  56.     get_NewEnum      dd ?
  57. ends
  58.  
  59. ; IID_IMatch2 Interface
  60. struct IMatch2
  61.     QueryInterface   dd ?
  62.     AddRef           dd ?
  63.     Release          dd ?
  64.  
  65.     ; IDispatch
  66.     GetTypeInfoCount dd ?
  67.     GetTypeInfo      dd ?
  68.     GetIDsOfNames    dd ?
  69.     _Invoke          dd ?
  70.  
  71.     ; IMatch2
  72.     get_Value        dd ?
  73.     get_FirstIndex   dd ?
  74.     get_Length       dd ?
  75.     get_SubMatches   dd ?
  76. ends
  77.  
  78. ; IID_ISubMatches Interface
  79. struct ISubMatches
  80.     QueryInterface   dd ?
  81.     AddRef           dd ?
  82.     Release          dd ?
  83.  
  84.     ; IDispatch
  85.     GetTypeInfoCount dd ?
  86.     GetTypeInfo      dd ?
  87.     GetIDsOfNames    dd ?
  88.     _Invoke          dd ?
  89.  
  90.     ; ISubMatches
  91.     get_Item         dd ?
  92.     get_Count        dd ?
  93.     get_NewEnum      dd ?
  94. ends
  95.  
  96. CLSCTX_INPROC_SERVER    = 1
  97. S_OK                    = 0
  98. VARIANT_TRUE            = 0x0000FFFF
Сразу переходим к программированию. За работу с регулярными выражениями отвечает интерфейс IRegExp, он инициализируется как и большинство стандартных COM-собъектов.
  1.         ; Инициализировать COM-объект
  2.         invoke  CoInitialize,NULL
  3.  
  4.         ; Создать объект
  5.         invoke  CoCreateInstance,CLSID_IRegExp,NULL,\
  6.                 CLSCTX_INPROC_SERVER,\
  7.                 IID_IRegExp2,pREDisp
  8.         cmp     eax,S_OK
  9.         ; Интерфейс VBScript Regular Expressions 5.5 недоступен
  10.         jne     error_not_supported
В интерфейсе IRegExp есть три метода для непосредственной работы с регулярными выражениями: Test, Execute и Replace. Остальные методы отвечают за установку и получение текущих настроек выполняемых операций: Global - выполнять операции над всеми найденными вхождениями, IgnoreCase - игнорировать регистр букв (аналог модификатора "i" в PCRE), Multiline - выполнять операцию с многострочными данными (аналог модификатора "m" в PCRE). Все три параметра имеют тип Boolean, устанавливаются они, соответственно, следующим образом:
  1.         ; Установить флаги выполняемой операции
  2.         mov     eax, [pREDisp]
  3.         mov     eax, [eax]
  4.         stdcall dword [eax+IRegExp2.set_Global],[pREDisp],\
  5.                 VARIANT_TRUE
  6.  
  7.         mov     eax, [pREDisp]
  8.         mov     eax, [eax]
  9.         stdcall dword [eax+IRegExp2.set_IgnoreCase],[pREDisp],\
  10.                 VARIANT_TRUE
  11.  
  12.         mov     eax, [pREDisp]
  13.         mov     eax, [eax]
  14.         stdcall dword [eax+IRegExp2.set_Multiline],[pREDisp],\
  15.                 VARIANT_TRUE
Получить и проверить текущее значение флагов можно примерно так:
  1.         ; Получить значение флага "Global"
  2.         mov     eax, [pREDisp]
  3.         mov     eax, [eax]
  4.         stdcall dword [eax+IRegExp2.get_Global],[pREDisp],\
  5.                 pBool
  6.  
  7.         ; Преобразовать знаковый WORD в DWORD
  8.         mov     eax,[pBool]
  9.         movsx   eax,ax
  10.         mov     [pBool],eax
  11.         ; Вот теперь можно сравнивать
  12.         cmp     [pBool],VARIANT_TRUE
Тут есть одна очень важная особенность. При установке флага используется значение размером DWORD, то есть для установки VARIANT_TRUE надо передавать 0xFFFFFFFF. Однако, при получении текущего значения флага, возвращается результат со значащим размером WORD. В приведенном примере после выполнения метода get_Global в переменную pBool будет записано значение 0x0000FFFF. Это надо учитывать при дальнейшей обработке этого значения или, например, для последующего сравнения.

Методы для работы с регулярными выражениями требуют предварительной инициализации строки этого самого выражения (паттерна). Она зафиксируется для всех последующих операций до момента ее изменения. Вот очень простенькая регулярка для поиска адресов электронной почты в тексте. Ее будем использовать в дальнейшем в качестве рабочего примера.
  1. szPattern du '([-_a-z0-9\.]+@[-_a-z0-9\.]+\.[a-z]+)',0
  2.         ...
  3.         ; Задать паттерн для регулярки
  4.         invoke  SysAllocString,szPattern
  5.         mov     [bstrP],eax
  6.         mov     eax, [pREDisp]
  7.         mov     eax, [eax]
  8.         stdcall dword [eax+IRegExp2.set_Pattern],[pREDisp],\
  9.                 [bstrP]
Здесь и далее все строки передаются только в формате юникода. Память под них выделяется при помощи функции SysAllocString.

Разбор методов интерфейса IRegExp начнем с самого простого - Test. Этот метод проверяет соответствие строки регулярному выражению и возвращает результат в виде логического значения.
  1. szTest du 'Please email me to mymail@gmail.com or nospam@mail.ru. I am waiting!',0
  2.         ...
  3.         ; Задать строку для проверки по регулярному выражению
  4.         invoke  SysAllocString,szTest
  5.         mov     [bstrT],eax
  6.         ; Выполнить проверку
  7.         mov     eax, [pREDisp]
  8.         mov     eax, [eax]
  9.         stdcall dword [eax+IRegExp2._Test],[pREDisp],\
  10.                 [bstrT],pBool
  11.  
  12.         ; Преобразовать знаковый WORD в DWORD
  13.         mov     eax,[pBool]
  14.         movsx   eax,ax
  15.         mov     [pBool],eax
  16.         cmp     [pBool],VARIANT_TRUE
  17.         ; Проверяемая строка соответствует регулярному выражению
  18.         je      pattern_found
  19.         ...
  20.         ...
  21.         ; Прибраться за собой
  22.         invoke SysFreeString,[bstrT]
В приведенном примере выполняется поиск в строке фрагментов, соответствующих адресу электронной почты. Если найдено хотя бы одно совпадение, то метод вернет значение "истина". Обратите внимание, что результат записывается в переменную размерностью в DWORD, тогда как значащие биты содержатся только в младшем WORD. Все в точности так же, как я рассказал в абзаце по работе с флагами.

Следующий метод Replace, как вы догадались из названия, выполняет замену в исходной строке по регулярному выражению. В качестве исходной строки возьмем строку из предыдущего примера и заменим в ней все адреса электронной почты на другое значение.
  1. szTest du 'Please email me to mymail@gmail.com or nospam@mail.ru. I am waiting!',0
  2. szReplace du 'NO_EMAILS_PLZ!',0
  3.         ...
  4.         ; Задать исходную строку
  5.         invoke  SysAllocString,szTest
  6.         mov     [bstrT],eax
  7.         ; Задать строку для замены по регулярному выражению
  8.         invoke  SysAllocString,szReplace
  9.         mov     [bstrR],eax
  10.         ; Выполнить замену
  11.         mov     eax, [pREDisp]
  12.         mov     eax, [eax]
  13.         stdcall dword [eax+IRegExp2.Replace],[pREDisp],\
  14.                 [bstrT],\
  15.                 VT_BSTR,0,[bstrR],0,\
  16.                 pResult
  17.         ...
  18.         ...
  19.         ; Прибраться за собой
  20.         invoke SysFreeString,[bstrT]
  21.         invoke SysFreeString,[bstrR]
  22.         invoke SysFreeString,[pResult]
В возвращаемой переменной pResult будет записан указатель на строку, в которой выполнены соответствующие замены. Важное замечание. Переменная типа VARIANT, в которой содержится информация о строке замены, передается не по указателю, а в виде четырех DWORD'ов, которые складываются на стек. С такими приколами я уже сталкивался раньше, но лишний час в отладчике все-таки пришлось провести.

При замене можно использовать обозначение субпаттернов. Например, вот такое регулярное выражение применяется для удаления повторяющихся элементов из строки.
  1. szTest    du 'one,two,three,three,four,five,five,six,seven,eight,eight',0
  2. szPattern du '([^,]*)(,\1)+(?=,|$)',0
  3. szReplace du '$1',0
Но было бы неразумно остановиться только на этих двух методах, ведь большинство задач с использованием регулярных выражений связаны именно с обработкой найденных соответствий. Вот так плавно мы переходим к последнему методу - Execute. Его реализация в простейшем, если можно так сказать, варианте, выглядит следующим образом:
  1. szTest du 'Please email me to mymail@gmail.com or nospam@mail.ru. I am waiting!',0
  2.         ...
  3.         ; Задать строку для проверки по регулярному выражению
  4.         invoke  SysAllocString,szTest
  5.         mov     [bstrT],eax
  6.  
  7.         ; Получить все найденные вхождения
  8.         mov     eax, [pREDisp]
  9.         mov     eax, [eax]
  10.         stdcall dword [eax+IRegExp2.Execute],[pREDisp],\
  11.                 [bstrT],pMatches
  12.  
  13.         ; Получить количество найденных вхождений
  14.         mov     eax, [pMatches]
  15.         mov     eax, [eax]
  16.         stdcall dword [eax+IMatchCollection2.get_Count],[pMatches],\
  17.                 dCount
  18.  
  19.         ; Что-то вообще найдено?
  20.         cmp     [dCount],0
  21.         je      error_nothing_found
  22.  
  23.         ; Перебрать поочередно все найденные вхождения
  24.         xor     ebx,ebx
  25. loc_loop:
  26.         ; Получить паттерн
  27.         mov     eax, [pMatches]
  28.         mov     eax, [eax]
  29.         stdcall dword [eax+IMatchCollection2.get_Item],[pMatches],\
  30.                 ebx,pMDisp
  31.  
  32.         ; Получить содержимое паттерна
  33.         mov     eax, [pMDisp]
  34.         mov     eax, [eax]
  35.         stdcall dword [eax+IMatch2.get_Value],[pMDisp],\
  36.                 pValue
  37.  
  38.         ; Получить позицию паттерна в строке
  39.         mov     eax, [pMDisp]
  40.         mov     eax, [eax]
  41.         stdcall dword [eax+IMatch2.get_FirstIndex],[pMDisp],\
  42.                 pPos
  43.  
  44.         ; Получить длину паттерна в символах
  45.         mov     eax, [pMDisp]
  46.         mov     eax, [eax]
  47.         stdcall dword [eax+IMatch2.get_Length],[pMDisp],\
  48.                 pLen
  49.         ...
  50.         ...
  51.  
  52.         ; Прибраться за собой
  53.         invoke SysFreeString,[pValue]
  54.  
  55.         ; Следующий паттерн
  56.         inc     ebx
  57.         ; Все вхождения обработаны?
  58.         cmp     ebx,[dCount]
  59.         jb      loc_loop
  60.  
  61.         ; Прибраться за собой
  62.         mov     eax,[pMatches]
  63.         mov     eax,[eax]
  64.         stdcall dword [eax+IMatchCollection2.Release],[pMatches]
  65.  
  66.         invoke SysFreeString,[bstrT]
После успешного вызова метода он возвращает указатель на интерфейс IMatchCollection2. С помощью свойства Count этого интерфейса можно получить количество найденных вхождений, а затем методом get_Item поочередно получить информацию о каждом вхождении. Этот метод в свою очередь возвращает указатель на интерфейс IMatch2, это и есть объект найденного паттерна. В данном примере нас интересуют только его свойства: Value - указатель на строку с найденным паттерном, FirstIndex - смещение найденного паттерна от начала строки и Length - длина паттерна в символах. Таким способом можно получить исчерпывающую информацию обо всех найденных вхождениях, соответствующих регулярному выражению.

Усложняем задачу. Регулярные выражения допускают вложенность, чтобы получить не только целые вхождения, но и разделять их на интересующие составные части. Например, в случае с адресами электронной почты это может быть имя почтового ящика, домен и доменная зона первого уровня, итого три уровня вложенности. Регулярное выражение для такой ситуации изменится следующим образом:
  1. szPattern du '(([-_a-z0-9\.]+)@([-_a-z0-9\.]+\.([a-z]+)))',0
В этом случае при перечислении найденных паттернов будет задействовано четвертое свойство интерфейса IMatch2 - SubMatches. Оно возвращает указатель на интерфейс ISubMatches, методы которого немного похожи на методы интерфейса IMatchCollection2. Точно так же можно получить количество субпаттернов, входящих в найденный паттерн, можно поочередно их перечислить. Основная разница в том, что метод get_Item интерфейса ISubMatches будет сразу возвращать указатель на строку субпаттерна. Получить позицию и длину субпаттерна через методы интерфейса не получится.
  1.         ; Получить субпаттерны
  2.         mov     eax, [pMDisp]
  3.         mov     eax, [eax]
  4.         stdcall dword [eax+IMatch2.get_SubMatches],\
  5.                 [pMDisp],pSubDisp
  6.  
  7.         ; Получить количество субпаттернов
  8.         mov     eax, [pSubDisp]
  9.         mov     eax, [eax]
  10.         stdcall dword [eax+ISubMatches.get_Count],\
  11.                 [pSubDisp],dCountSub
  12.  
  13.         ; Что-то вообще найдено?
  14.         cmp     [dCountSub],0
  15.         je      error_no_subpatterns_found
  16.  
  17.         ; Перебрать поочередно все найденные вхождения
  18.         xor     ebx,ebx
  19. loc_sub:
  20.         ; Получить содержимое субпаттерна
  21.         mov     eax, [pSubDisp]
  22.         mov     eax, [eax]
  23.         stdcall dword [eax+ISubMatches.get_Item],\
  24.                 [pSubDisp],ebx,bVariant
  25.  
  26.         ; [bVariant.lVal] -> указатель на строку 
  27.  
  28.         inc     ebx
  29.         cmp     ebx,[dCountSub]
  30.         jb      loc_sub
Независимо от количества уровней вложенности, все субпаттерны обрабатываются за один проход цикла. При этом содержимое самого первого субпаттерна в точности совпадает с родительским паттерном.

Как видите, используя возможности IRegExp, можно весьма эффективно работать с регулярными выражениями. К сожалению, не обошлось без минусов, к примеру, IRegExp не поддерживает работу с ретроспективными проверками. Если для вас это критично, то придется воспользоваться пред- или постобработкой данных, или же поискать более другие инструменты для работы с регулярными выражениями. Но с остальными задачами IRegExp справляется отлично, не требуя при этом написания больших объемов сложного кода.

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

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

RegExp.Demo.zip (5,385 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 367 | Комментариев: 1

Комментарии

Отзывы посетителей сайта о статье
morgot (20.11.2024 в 12:41):
Спасибо, полезная вещь.
Почему в винапи за столько лет не встроили регулярки - загадка.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.004 сек.) / Память: 4.5 Mb
Наверх