Разработка сайта для Вашего бизнеса. Веб дизайн. Дизайн логотипа, фирменного стиля, рекламная фотография . Комплексный рекламный креатив.

Ralex. We do the work.
На рынке с 1999го года. Средняя ценовая категория. Ориентация на эффективность решений.
Ознакомтесь с нашим портфолио
Узнайте больше о услугах
Свяжитесь с нами:
E-mail: [email protected]
Tel: (044) 587 - 84 - 78
Custom web design & дизайн и разработка сайта "под ключ"
Креативный, эффективный дизайн. Система управления сайтом (СУС).
Custom flexible разработка систем электронной коммерции
Система e-commerce разрабатывается под индивидуальные потребности. Гибкая функциональность.
Search Engine Optimzation & оптимизация под поисковые системы (SEO)
Постоянная оптимизация и мониторинг сайта в поисковых системах. Достигаем результата быстро и эффективно
Custom logo design & дизайн логотипа и фирменного стиля
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.
профессиональная рекламная фотография
креативно, смело, качественно
Custom logo design & рекламный креатив. дизайн рекламы
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.

Регулярні вирази і вирази подяки Робу Пайка. «Santa Simplicita

  1. Найпростіший движок регулярних виразів
  2. Витягуємо більше користі
  3. замість висновків

Наша взаимовыгодная связь https://banwar.org/

Я думаю всі чули про Регулярні вирази і знають навіщо вони потрібні - по суті вони дозволяють дізнатися чи відповідає рядок регулярному виразу, і якщо так - то які саме ділянки рядки відповідають яких ділянках вираження.

А що якщо написати свій движок регулярних виразів?

Отже, згадаємо, що регулярні вирази - це по суті кінцевий автомат. Коли ми зустрічаємо якийсь символ - автомат переходить в новий стан (якщо це можливо). Наприклад, для вираження ab (cd | ef) g вийде такий автомат:

Я не буду вдаватися в подробиці про детермінованих і недетермінірованних автоматах. Просто будемо пам'ятати, що саме слово описує всілякі стану і зв'язку між ними, а вхідний рядок буква за буквою перемикає стану автомата. Тут, наприклад, в стані 2 якщо на вхід надійде буква 'd' - перейдемо в стан 3, якщо буква 'c' - в стан 4. Іноді можна буде сказати в який стан переходити не заглядаючи наперед рядки.

Найпростіший движок регулярних виразів

Існує чудова книжка Роба Пайка і (здається) Брайна Керніган - The Practice Of Programming (TPOP) . Там наводиться код найпростішого движка для регулярних виразів. Кому лінь читати всю книгу - тут описаний основний підхід і наводяться ті самі легендарні 30 рядків коду.

Движок здатний розпізнавати початок / кінець рядка і задавати wildcard - маску для безлічі однакових символів.
Геніальність цієї реалізації в рекурсивном підході. Код виходить дуже лаконічний, а різниця в швидкості майже не помітна.

Витягуємо більше користі

Я вирішив використовувати цей код за основу, але отримати трохи більше, а саме:

  • Дізнаватися де починається відповідність і скільки символів рядка потрапляє під вираз
  • Додати спеціальні групи символом - ну хоча б \ d для цифр і \ s для прогалин
  • Додати квантіфікатори (ну або як це назвати?) Для знаходження 0 і більше входжень ( «*»), 1 і більше ( «+»), 0 або 1 ( «?») І жодного оператора для 0 і більше ( «-» - як в Lua)

Спробуємо вкластися рядків десь в 100.

Починати будемо так само - нам потрібна єдина головна функція і функція, яка визначає відповідність початку рядка висловом - regex_match і regex_match_here відповідно:

int regex_match (const char * regex, char * s, int * len) {char * p = s; / * Force match from the beginning of the string * / if (regex [0] == '^') {return (regex_match_here (regex + 1, s, len)? 0: -1); } / * Iterate the string to find matching position * / do {* len = 0; if (regex_match_here (regex, p, len)) {return p - s; }} While (* p ++! = '\ 0'); return -1; } Int regex_match_here (const char * regex, char * s, int * len) {// TODO}

З regex_match все ясно, якщо «^» - перевіряємо початок рядка, інакше - танцюємо зліва направо поки не знайдемо відповідність.

Найголовніше знаходиться в regex_match_here:

static int regex_match_here (const char * regex, char * s, int * len) {int c = regex [0]; if (regex [0] == '\ 0') {/ * end of regex = full match * / return 1; } Else if (regex [0] == '$' && regex [1] == '\ 0') {/ * check end of string * / return (* s == '\ 0'); } Else if (regex [0] == '\\' && regex [1]! = '\ 0') {/ * check escaped symbol * / c = regex [1]; if (c! = '^' && c! = '$' && c! = '\\' && c! = '+' && c! = '*' && c! = '-' && c! = '? ') {c = c | 0x100; } Regex = regex + 1; } / * Check for special operators *, +,?, - * / if (regex [1] == '*' || regex [1] == '+' || regex [1] == '-' | | regex [1] == '?') {return regex_match_quantity (regex [1], c, regex + 2, s, len); } Else if (* s! = '\ 0' && regex_match_group (* s, c)) {* len = * len + 1; return regex_match_here (regex + 1, s + 1, len); } Return 0; }

Тут перевіряємо стан нашого неявного автомата:

  • якщо вираз закінчилося (досягнуто кінцевий стан) - повернути 1, відповідність знайдено
  • якщо в регулярному виразі дійшли до останнього символу, і це - «$» (тобто кінець рядка) - повернути 1 якщо рядок теж закінчилася.
  • якщо знайшли «\» - екранує символ, але ми маємо справу з особливою групою символів. Взагалі, в звичайному синтаксисі «\» грає дві ролі - для «\.», «\ ^», «\ $», '\?', '\ *', '\ +', '\ -' він означає використовувати просто символи «.», «^» і т.д. замість з мета-значень ( «будь-який символ», «початок рядка», «кінець рядка», ...). Для інших ( «\ d», «\ s», ...) він означає використовувати мета-значення ( «будь-яка цифра», «будь-який символ пробілу», ...). Домовимося, що значення вище 256 означатимуть мета-символ, тобто ( 'D' | 0x100) - це «будь-яка цифра», а 'd' - буква 'd'. Ах да, з точною ( «.») Все буде навпаки. Ну і синтаксис.
  • отже, символ або групу ми вважали. Тепер перевіряємо оператори «+», «*», «?» І «-«. Для них - особлива функція перевірки regex_match_quantity, а в інших випадках - просто перевіряємо чи потрапляє наступний символ рядка в групу і йде далі (рекурсивно).

Функція перевірки попадання символу в групу проста і легко розширюється:

static int regex_match_group (int c, int group) {if ((group & 0xff) == '.') group ^ = 0x100; if (group <0x100) {/ * a single char * / return c == group; } / * A meta char, like \ d, ... * / switch (group & 0xff) {case 'd': return isdigit (c); case 's': return isspace (c); case 'D': return! isdigit (c); case 'S': return! isspace (c); case '.': return 1; } Return 0; }

У цій функції ми дивимося - для простих символів крім точки - просто порівнюємо. Для інших - перевіряємо групи. З точкою - все навпаки. Я використовую функції з ctypes.h, але для простих груп можна і без них обійтися.

Ну і самий кошмарний код в функції regex_match_quantity:

static int regex_match_quantity (int quant, int c, const char * regex, char * s, int * len) {if (quant == '?') {if (regex_match_group (* s, c)) {* len = * len + 1; s = s + 1; } Return regex_match_here (regex, s, len); } If (quant == '+' || quant == '*') {/ * match as much as possible * / char * p; for (p = s; * p! = '\ 0' && regex_match_group (* p, c); p ++) {* len = * len + 1; } If (quant == '+' && p == s) {return 0; } Do {if (regex_match_here (regex, p, len)) {return 1; } * Len = * len - 1; } While (p--> s); } Else if (quant == '-') {/ * match as little as possible * / do {if (regex_match_here (regex, s, len)) {return 1; } * Len = * len + 1; } While (* s! = '\ 0' && regex_match_group (* s ++, c)); } Return 0; }

Ну для «?» Завжди повертаємо 1, але якщо символ дійсно входить в групу - збільшуємо довжину збігається регіону на 1.

Для жадібних операторів «+» і «*», які захоплюють якомога більше символів рядка - так і робимо. Перевіряємо скільки взагалі символів рядка може потрапити в задану групу, а потім повертаємося назад поки не знайдемо відповідність залишку рядка залишку регулярного виразу. Для «+» хоча б один символ рядка має потрапити в групу.

Для нежадібні оператора «-» просто йде зліва направо поки не співпаде залишок рядка із залишком вираження.

От і все. У мене вийшло 98 рядків. Жодної залежності. З libc - тільки ctype.h, тобто при особливому вмінні збереться навіть якимось неповноцінним SmallC. Також ніяких особливих булочок З я не використовував, код повинен бути переносимо і на інші мови, на той же Forth.

Весь код - як завжди на bitbucket .
Там же і жменька тестів щоб бути впевненим що все працює як треба.

замість висновків

Спасибі Робу Пайку за елегантний і корисний код. Я розумію, що для серйозних завдань такі мікро-регекспів не годяться, але для чогось простенького в embedded - цілком. Тим більше особисто я вважаю, що не варто робити і регулярних виразів тьюірнг-повний мега-мову, вже краще б був спосіб комбінування простих регулярних виразів з домішкою деякої простої логіки типу умовних-циклів.

Тут до речі теж варто відзначити роботу Роба Пайка про структурні регулярних виразах . І його текстовий редактор Sam барвистий приклад їх простоти і потужності. Спасибі, Роб.

PS Окремо дякую, Роб, за мову Go 🙂

А що якщо написати свій движок регулярних виразів?
Ну або як це назвати?
«?
Regex [1] == '*' || regex [1] == '+' || regex [1] == '-' | | regex [1] == '?
», «\ ^», «\ $», '\?
Категории
  • Биология
  • Математика
  • Краеведению
  • Лечебная
  • Наука
  • Физике
  • Природоведение
  • Информатика
  • Новости

  • Новости
    https://banwar.org/
    Наша взаимовыгодная связь https://banwar.org/. Запустив новый сайт, "Пари Матч" обещает своим клиентам незабываемый опыт и возможность выиграть крупные суммы.


    Наши клиенты
    Клиенты

    Быстрая связь

    Тел.: (044) 587-84-78
    E-mail: [email protected]

    Имя:
    E-mail:
    Телефон:
    Вопрос\Комментарий: