Поскольку вы хотите узнать, как работают лексеры, я полагаю, вы действительно хотите знать, как работают генераторы лексеров.
Генератор лексера принимает лексическую спецификацию, которая представляет собой список правил (пары регулярных выражений-токенов), и генерирует лексер. Этот результирующий лексер может затем преобразовать входную (символьную) строку в строку токена в соответствии с этим списком правил.
Наиболее часто используемый метод состоит в преобразовании регулярного выражения в детерминированные конечные автоматы (DFA) с помощью недетерминированных автоматов (NFA) плюс несколько деталей.
Подробное руководство по выполнению этой трансформации можно найти здесь . Обратите внимание, что я сам не читал, но выглядит довольно хорошо. Кроме того, практически в любой книге по построению компилятора это преобразование будет описано в первых нескольких главах.
Если вас интересуют слайды лекционных курсов по данной теме, их, несомненно, можно найти в бесчисленном количестве курсов по построению компиляторов. В моем университете вы можете найти такие слайды здесь и здесь .
Есть еще несколько вещей, которые обычно не используются в лексерах или не рассматриваются в текстах, но тем не менее весьма полезны:
Во-первых, обработка Unicode несколько нетривиальна. Проблема состоит в том, что вход ASCII имеет ширину всего 8 бит, что означает, что вы можете легко иметь таблицу переходов для каждого состояния в DFA, поскольку они имеют только 256 записей. Однако для Unicode шириной 16 бит (если вы используете UTF-16) требуются таблицы по 64 КБ для каждой записи в DFA. Если у вас сложные грамматики, это может начать занимать довольно много места. Заполнение этих таблиц также начинает занимать довольно много времени.
Кроме того, вы можете генерировать интервальные деревья. Например, дерево диапазона может содержать кортежи ('a', 'z'), ('A', 'Z'), что намного более эффективно использует память, чем заполненная таблица. Если вы поддерживаете непересекающиеся интервалы, вы можете использовать любое сбалансированное двоичное дерево для этой цели. Время выполнения является линейным по количеству битов, необходимых для каждого символа, поэтому O (16) в случае Unicode. Однако, в лучшем случае, это обычно будет немного меньше.
Еще одна проблема заключается в том, что лексеры, которые обычно генерируются, на самом деле имеют квадратичную производительность в худшем случае. Хотя это наихудшее поведение обычно не наблюдается, оно может вас укусить. Если вы столкнулись с проблемой и хотите ее решить, документ , описывающий , как достичь линейного времени можно найти здесь .
Возможно, вы захотите описать регулярные выражения в виде строк, как они обычно появляются. Однако анализ этих описаний регулярных выражений в NFA (или, возможно, сначала в рекурсивной промежуточной структуре) представляет собой проблему куриного яйца. Для анализа описаний регулярных выражений очень подходит алгоритм Shunting Yard. В Википедии, кажется, есть обширная страница об алгоритме .