Регулярні вирази не можна використовувати для обробки дуже складних рядків, які мають граматику, таких як вихідний код мови програмування, анотації, що описують складені типи даних для методів, математичні вирази, обчислення, формули та інше. Причина в тому, що це настільки складні рядкові форми, які містять багато правил, що ми просто змушені обробляти їх меншими шматками.
Коли комп'ютер обробляє вихідний код PHP, наприклад, він спочатку розбиває його на безліч дрібних частин, які несуть свій власний сенс. Ці частини називаються "токенами", і вони являють собою найменші самодостатні будівельні блоки мови.
Принцип обробки рядка/мови поділяється на кілька етапів:
Ще однією великою перевагою такого підходу є те, що ми знаємо позицію токена в рядку (як рядок, так і конкретний символ початку і кінця токена), коли ми проходимо через токен, тому ми можемо точно визначити місце розташування проблеми, якщо буде згенеровано виключення.
Уявіть собі, наприклад, що ви реалізуєте алгоритм розв'язання математичного прикладу. Математика має багато правил, таких як пріоритети операторів, дужки, виклики функцій і так далі.
Якщо ми можемо розбити вхідний рядок на елементарні маркери, ми можемо працювати з ним на зовсім іншому рівні. Наприклад, ми можемо легко знаходити окремі дужки, віднімати лексеми від початкової дужки до кінцевої, передавати підвираз рекурсивній функції для обробки і так далі.
Токенізація дозволяє дуже елегантно вирішувати навіть складні завдання синтаксичного аналізу.
Нам не потрібно стільки знань, щоб написати власний токенізатор. В принципі, нам достатньо знати принцип роботи регулярних виразів і написати невеликий об'єкт парсингу.
Для цілей цієї статті я підготував базову версію токенізатора на основі токенізатора Latte (Nette). Автором оригінальної реалізації є David Grudl, якому я хотів би подякувати за таку просту функцію, яка вирішує всі проблеми за вас.
final class Token{public string $value;public int $offset;public string $type;}final class Tokenizer{public const TokenTypes = ['масив' => 'масив','<' => '\<','>' => '\>','{' => '\{','}' => '\}','або' => '\|','список' => '\[\]','тип' => '[a-zA-Z]+','простір' => '\s+','кома' => ',','інший' => '.+?',];/*** @повертається масив<int, Token>*/public static function tokenize(string $haystack): array{$re = '~(' . implode(')|(', self::TokenTypes) . ')~A';$types = array_keys(self::TokenTypes);preg_match_all($re, $haystack, $tokenMatch, PREG_SET_ORDER);$len = 0;$count = count($types);$tokens = [];foreach ($tokenMatch as $match) {$type = null;for ($i = 1; $i <= $count; $i++) {if (isset($match[$i]) === false) {break;}if ($match[$i] !== '') {$type = $types[$i - 1];break;}}$token = new Token;$token->value = $match[0];$token->offset = $len;$token->type = (string) $type;$tokens[] = $token;$len += strlen($match[0]);}if ($len !== strlen($haystack)) {$text = substr($haystack, 0, $len);$line = substr_count($text, "\n") + 1;$col = $len - strrpos("\n" . $text, "\n") + 1;$token = str_replace("\n", '\n', substr($haystack, $len, 10));throw new \LogicException(sprintf('Неочікувані "%s" в рядку %s, стовпці %s.', $token, $line, $col));}return $tokens;}}
Цей токенізатор може розібрати, наприклад, такий складний рядок (формат навмисно перемежовується пробілами, щоб показати, що токенізатор може обробляти великий діапазон випадків):
array<int, array<bool, array<string, float> >
Jan Barášek Více o autorovi
Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.
Rád vám pomůžu:
Články píše Jan Barášek © 2009-2024 | Kontakt | Mapa webu
Status | Aktualizováno: ... | uk