Суть конструкции Томпсона заключается в том, что он создает NFA со следующими характеристиками:
Существует не более 2|R|
состояний, где |R|
- длина регулярного выражения .
Каждое состояние имеет либо один выход, помеченный символом, либо не более двух ε-переходов. (То есть ни одно состояние не имеет как помеченного перехода, так и перехода ε.)
Последний факт предполагает, что представление состояния в виде
struct State {
std::vector<std::tuple<char, State*>> transitions;
}
(что небольшая аббревиатура вашего кода) - это очень высокое представление служебной информации, где служебная информация имеет гораздо большее отношение к служебной информации std::vector
, используемой для хранения ровно одного или двух переходов, чем заполнение одного перехода. Кроме того, вышеприведенное представление не обеспечивает четкого метода для представления ε-переходов, если только не было намерения зарезервировать один символьный код для ε (и тем самым сделать невозможным использование этого символьного кода в регулярном выражении).
Более практичным представлением может быть
enum class StateType { EPSILON, IMPORTANT };
struct State {
StateType type;
char label;
State* next[2];
};
(Эта формулировка не хранит число переходов в next
, при условии, что мы можем использовать значение часового, чтобы указать, что next[1]
не Кроме того, мы могли бы просто установить next[1] = next[0];
в таком случае. Помните, что это имеет значение только для состояний ε.)
Более того, поскольку мы знаем, что в объектах не более 2|R|
State
NFA, мы могли бы заменить указатели State*
на маленькие целые числа. Это установит какое-то ограничение на размер регулярного выражения, которое может быть обработано, но довольно редко встречаются регулярные выражения в гигабайтах. Использование последовательных целых чисел, а не указателей также сделает некоторые алгоритмы графов более управляемыми, в частности алгоритм транзитивного замыкания, который является фундаментальным для построения подмножества.
Еще один интересный факт о NFA, построенном по алгоритму Томпсона, заключается в том, что - степень состояний также ограничена 2 (и опять же, если есть два перехода, оба будут ε переходами). Это позволяет нам избежать преждевременного создания конечных состояний вспомогательных машин (что не потребуется, если вспомогательный механизм является левым аргументом для конкатенации). Вместо этого мы можем представить вспомогательную машину всего тремя индексами: индексом начального состояния и индексами не более двух внутренних состояний, которые после добавления будут иметь переходы в конечное состояние.
Я думаю, что вышеупомянутое достаточно близко к первоначальной реализации Томпсона, хотя я уверен, что он использовал гораздо больше приемов оптимизации. Но стоит прочитать Раздел 3.9 Aho, Lam, Sethi & Ullman («Книга Дракона»), в котором описаны способы оптимизации построения конечных автоматов.
Независимо от теоретических сокращений, стоит отметить, что помимо tr ie шаблонов ключевых слов, большинство переходов состояний в лексическом анализе включают наборы символов, а не отдельные символы, и часто эти наборы довольно велики, особенно если единицей лексического анализа является кодовая точка Unicode, а не символ ascii. Использование наборов символов вместо символов усложняет алгоритм построения подмножества, но, как правило, значительно уменьшает количество состояний.