Работа с регулярными выражениями на Ассемблере
Работа с регулярными выражениями на Ассемблере
Без преувеличения, регулярные выражения - один из самых мощных инструментов для обработки как текстовой, так и двоичной информации. В настоящее время есть немало различных внешних библиотек, классов и компонентов для работы с регулярками, но не стоит забывать, что в арсенале программиста есть и встроенные в систему средства, которые позволяют реализовать практически все мощности этой технологии. Вот сегодня о них и поговорим.
В системе за регулярки отвечают инструменты, предоставляемые VBScript Regular Expression. Они доступны в системах, в которых есть Internet Explorer 5.5 и выше, то есть любая версия после Windows 98. У нас сегодня работа с COM, поэтому традиционно начинаем с описания GUID, интерфейсов и констант, которых нет в стандартном пакете FASM.
Code (Assembler) : Убрать нумерацию
- ; GUID {3F4DACB0-160D-11D2-A8E9-00104B365C9F}
- IID_IRegExp2 \
- dd 03F4DACB0h
- dw 0160Dh
- dw 011D2h
- db 0A8h, 0E9h, 000h, 010h, 04Bh, 036h, 05Ch, 09Fh
- ; GUID {3F4DACA4-160D-11D2-A8E9-00104B365C9F}
- CLSID_IRegExp \
- dd 03F4DACA4h
- dw 0160Dh
- dw 011D2h
- db 0A8h, 0E9h, 000h, 010h, 04Bh, 036h, 05Ch, 09Fh
- ; IID_IRegExp2 Interface
- struct IRegExp2
- QueryInterface dd ?
- AddRef dd ?
- Release dd ?
- ; IDispatch
- GetTypeInfoCount dd ?
- GetTypeInfo dd ?
- GetIDsOfNames dd ?
- _Invoke dd ?
- ; IRegExp2
- get_Pattern dd ?
- set_Pattern dd ?
- get_IgnoreCase dd ?
- set_IgnoreCase dd ?
- get_Global dd ?
- set_Global dd ?
- get_Multiline dd ?
- set_Multiline dd ?
- Execute dd ?
- _Test dd ?
- Replace dd ?
- ends
- ; IID_IMatchCollection2 Interface
- struct IMatchCollection2
- QueryInterface dd ?
- AddRef dd ?
- Release dd ?
- ; IDispatch
- GetTypeInfoCount dd ?
- GetTypeInfo dd ?
- GetIDsOfNames dd ?
- _Invoke dd ?
- ; IMatchCollection2
- get_Item dd ?
- get_Count dd ?
- get_NewEnum dd ?
- ends
- ; IID_IMatch2 Interface
- struct IMatch2
- QueryInterface dd ?
- AddRef dd ?
- Release dd ?
- ; IDispatch
- GetTypeInfoCount dd ?
- GetTypeInfo dd ?
- GetIDsOfNames dd ?
- _Invoke dd ?
- ; IMatch2
- get_Value dd ?
- get_FirstIndex dd ?
- get_Length dd ?
- get_SubMatches dd ?
- ends
- ; IID_ISubMatches Interface
- struct ISubMatches
- QueryInterface dd ?
- AddRef dd ?
- Release dd ?
- ; IDispatch
- GetTypeInfoCount dd ?
- GetTypeInfo dd ?
- GetIDsOfNames dd ?
- _Invoke dd ?
- ; ISubMatches
- get_Item dd ?
- get_Count dd ?
- get_NewEnum dd ?
- ends
- CLSCTX_INPROC_SERVER = 1
- S_OK = 0
- VARIANT_TRUE = 0x0000FFFF
Code (Assembler) : Убрать нумерацию
- ; Инициализировать COM-объект
- invoke CoInitialize,NULL
- ; Создать объект
- invoke CoCreateInstance,CLSID_IRegExp,NULL,\
- CLSCTX_INPROC_SERVER,\
- IID_IRegExp2,pREDisp
- cmp eax,S_OK
- ; Интерфейс VBScript Regular Expressions 5.5 недоступен
- jne error_not_supported
Code (Assembler) : Убрать нумерацию
- ; Установить флаги выполняемой операции
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.set_Global],[pREDisp],\
- VARIANT_TRUE
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.set_IgnoreCase],[pREDisp],\
- VARIANT_TRUE
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.set_Multiline],[pREDisp],\
- VARIANT_TRUE
Code (Assembler) : Убрать нумерацию
- ; Получить значение флага "Global"
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.get_Global],[pREDisp],\
- pBool
- ; Преобразовать знаковый WORD в DWORD
- mov eax,[pBool]
- movsx eax,ax
- mov [pBool],eax
- ; Вот теперь можно сравнивать
- cmp [pBool],VARIANT_TRUE
Методы для работы с регулярными выражениями требуют предварительной инициализации строки этого самого выражения (паттерна). Она зафиксируется для всех последующих операций до момента ее изменения. Вот очень простенькая регулярка для поиска адресов электронной почты в тексте. Ее будем использовать в дальнейшем в качестве рабочего примера.
Code (Assembler) : Убрать нумерацию
- szPattern du '([-_a-z0-9\.]+@[-_a-z0-9\.]+\.[a-z]+)',0
- ...
- ; Задать паттерн для регулярки
- invoke SysAllocString,szPattern
- mov [bstrP],eax
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.set_Pattern],[pREDisp],\
- [bstrP]
Разбор методов интерфейса IRegExp начнем с самого простого - Test. Этот метод проверяет соответствие строки регулярному выражению и возвращает результат в виде логического значения.
Code (Assembler) : Убрать нумерацию
- szTest du 'Please email me to mymail@gmail.com or nospam@mail.ru. I am waiting!',0
- ...
- ; Задать строку для проверки по регулярному выражению
- invoke SysAllocString,szTest
- mov [bstrT],eax
- ; Выполнить проверку
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2._Test],[pREDisp],\
- [bstrT],pBool
- ; Преобразовать знаковый WORD в DWORD
- mov eax,[pBool]
- movsx eax,ax
- mov [pBool],eax
- cmp [pBool],VARIANT_TRUE
- ; Проверяемая строка соответствует регулярному выражению
- je pattern_found
- ...
- ...
- ; Прибраться за собой
- invoke SysFreeString,[bstrT]
Следующий метод Replace, как вы догадались из названия, выполняет замену в исходной строке по регулярному выражению. В качестве исходной строки возьмем строку из предыдущего примера и заменим в ней все адреса электронной почты на другое значение.
Code (Assembler) : Убрать нумерацию
- szTest du 'Please email me to mymail@gmail.com or nospam@mail.ru. I am waiting!',0
- szReplace du 'NO_EMAILS_PLZ!',0
- ...
- ; Задать исходную строку
- invoke SysAllocString,szTest
- mov [bstrT],eax
- ; Задать строку для замены по регулярному выражению
- invoke SysAllocString,szReplace
- mov [bstrR],eax
- ; Выполнить замену
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.Replace],[pREDisp],\
- [bstrT],\
- VT_BSTR,0,[bstrR],0,\
- pResult
- ...
- ...
- ; Прибраться за собой
- invoke SysFreeString,[bstrT]
- invoke SysFreeString,[bstrR]
- invoke SysFreeString,[pResult]
При замене можно использовать обозначение субпаттернов. Например, вот такое регулярное выражение применяется для удаления повторяющихся элементов из строки.
Code (Assembler) : Убрать нумерацию
- szTest du 'one,two,three,three,four,five,five,six,seven,eight,eight',0
- szPattern du '([^,]*)(,\1)+(?=,|$)',0
- szReplace du '$1',0
Code (Assembler) : Убрать нумерацию
- szTest du 'Please email me to mymail@gmail.com or nospam@mail.ru. I am waiting!',0
- ...
- ; Задать строку для проверки по регулярному выражению
- invoke SysAllocString,szTest
- mov [bstrT],eax
- ; Получить все найденные вхождения
- mov eax, [pREDisp]
- mov eax, [eax]
- stdcall dword [eax+IRegExp2.Execute],[pREDisp],\
- [bstrT],pMatches
- ; Получить количество найденных вхождений
- mov eax, [pMatches]
- mov eax, [eax]
- stdcall dword [eax+IMatchCollection2.get_Count],[pMatches],\
- dCount
- ; Что-то вообще найдено?
- cmp [dCount],0
- je error_nothing_found
- ; Перебрать поочередно все найденные вхождения
- xor ebx,ebx
- loc_loop:
- ; Получить паттерн
- mov eax, [pMatches]
- mov eax, [eax]
- stdcall dword [eax+IMatchCollection2.get_Item],[pMatches],\
- ebx,pMDisp
- ; Получить содержимое паттерна
- mov eax, [pMDisp]
- mov eax, [eax]
- stdcall dword [eax+IMatch2.get_Value],[pMDisp],\
- pValue
- ; Получить позицию паттерна в строке
- mov eax, [pMDisp]
- mov eax, [eax]
- stdcall dword [eax+IMatch2.get_FirstIndex],[pMDisp],\
- pPos
- ; Получить длину паттерна в символах
- mov eax, [pMDisp]
- mov eax, [eax]
- stdcall dword [eax+IMatch2.get_Length],[pMDisp],\
- pLen
- ...
- ...
- ; Прибраться за собой
- invoke SysFreeString,[pValue]
- ; Следующий паттерн
- inc ebx
- ; Все вхождения обработаны?
- cmp ebx,[dCount]
- jb loc_loop
- ; Прибраться за собой
- mov eax,[pMatches]
- mov eax,[eax]
- stdcall dword [eax+IMatchCollection2.Release],[pMatches]
- invoke SysFreeString,[bstrT]
Усложняем задачу. Регулярные выражения допускают вложенность, чтобы получить не только целые вхождения, но и разделять их на интересующие составные части. Например, в случае с адресами электронной почты это может быть имя почтового ящика, домен и доменная зона первого уровня, итого три уровня вложенности. Регулярное выражение для такой ситуации изменится следующим образом:
Code (Assembler) : Убрать нумерацию
- szPattern du '(([-_a-z0-9\.]+)@([-_a-z0-9\.]+\.([a-z]+)))',0
Code (Assembler) : Убрать нумерацию
- ; Получить субпаттерны
- mov eax, [pMDisp]
- mov eax, [eax]
- stdcall dword [eax+IMatch2.get_SubMatches],\
- [pMDisp],pSubDisp
- ; Получить количество субпаттернов
- mov eax, [pSubDisp]
- mov eax, [eax]
- stdcall dword [eax+ISubMatches.get_Count],\
- [pSubDisp],dCountSub
- ; Что-то вообще найдено?
- cmp [dCountSub],0
- je error_no_subpatterns_found
- ; Перебрать поочередно все найденные вхождения
- xor ebx,ebx
- loc_sub:
- ; Получить содержимое субпаттерна
- mov eax, [pSubDisp]
- mov eax, [eax]
- stdcall dword [eax+ISubMatches.get_Item],\
- [pSubDisp],ebx,bVariant
- ; [bVariant.lVal] -> указатель на строку
- inc ebx
- cmp ebx,[dCountSub]
- jb loc_sub
Как видите, используя возможности IRegExp, можно весьма эффективно работать с регулярными выражениями. К сожалению, не обошлось без минусов, к примеру, IRegExp не поддерживает работу с ретроспективными проверками. Если для вас это критично, то придется воспользоваться пред- или постобработкой данных, или же поискать более другие инструменты для работы с регулярными выражениями. Но с остальными задачами IRegExp справляется отлично, не требуя при этом написания больших объемов сложного кода.
В приложении пример программы с исходным текстом, которая работает со строкой, используя регулярные выражения.
Просмотров: 367 | Комментариев: 1
Комментарии
Отзывы посетителей сайта о статье
Добавить комментарий
Заполните форму для добавления комментария
Почему в винапи за столько лет не встроили регулярки - загадка.