Регулярні вирази
Невеличка стаття на примітивно-базовому рівні про те, як працюють регулярні вирази. Я хотів би написати серію про створення власного транслятора і це може бути одним із таких постів. Тут я опишу коротку програму, яка буде перевіряти вхідний рядок на відповідність нашому заданому регулярному виразу.
Регулярний вираз - це граматика, що задає мову. Простіше кажучи, це група правил, записаних у вигляді набору символів, що визначає, якого вигляду конструкції може містити певна задана мова.
Щоб написати програму, що буде розбирати регулярний вираз, потрібно розуміти поняття автомату.
Автомат - це сутність, що визначається такими поняттями:
M = (Q, T, q0, d, F)
Q
- множина всіх станів автомата.
T
- множина вхідних символів.
q0
- початковий стан (входить в множину Q).
d
- функція переходів автомата.
F
- множина кінцевих станів автомата (підмножина Q).
Побудуємо автомат, що буде розбирати регулярний вираз E = (+ | - | e) d d*
.
E
(expression) - регулярний вираз.
e
(empty) - порожній рядок.
d
(digit) - цифра.
|
(вертикальна риска, pipe) - операція або. Повинен бути присутній хоча б один символ з перелічених.
*
- символ може повторюватися довільну кількість разів (від нуля до нескінченності).
Потрібно виділити всі можливі стани і відповідні переходи до них. Найпростіше це подати графічно:
S
(start) - це початковий стан, F
(finish) - кінцевий. З початкового стану ми можемо прочитати символи додавання, віднімання чи нічого (порожній символ) і перейти в стан B
зчитування цифри. В стані C
у нас уже є гарантовано одна цифра і ми переходимо безумовно (по порожньому символу) в стан D
. В ньому ми можемо крутитися в циклі доти, доки зустрічаємо цифру або ж одразу перейти в кінцевий стан F
. Тепер ми знаємо всі можливі умови і переходи, які можна записати кодом:
1 | class InvalidInput(ValueError): |
Все що тут відбувається - це посимвольне зчитування вхідних даних і перевірка на відповідність поточного символа очікуваному стану. Якщо символ не відповідає стану - отримаємо помилку вхідних даних InvalidInput. Даною програмою тепер можна перевіряти рядки на відповідність нашому регулярному виразу. Так всередині влаштовані багато інструментів розбору регулярок.
Але коли регулярка стає складнішою, а вхідний текст довшим - хочеться мати змогу швидко знайти місце помилки в разі невірних вхідних даних. Таку функціональність ми спробуємо додати до нашого аналізатору.
Створимо клас
1 | class Matcher(object): |
що буде зберігати позицію останнього зчитаного символу. Також додамо два методи для отримання наступного символу і відображення помилки
1 | def read(self): |
І сам метод match
майже без модифікацій
1 | def match(self): |
Тепер коли виникне помилка - ми побачимо інформативне повідомлення з вказанням місця в рядку, де вона була виявлена.