76
Имеется другая, более практическая причина для отделения сканера от
синтаксического анализатора. Мы хотим думать о входном исходном файле как потоке
символов, которые мы обрабатываем справа налево без возвратов. На практике это
невозможно. Почти каждый язык имеет некоторые ключевые слова типа IF, WHILE и END.
Как я упомянул ранее, в действительности мы не можем знать является ли данная строка
ключевым словом до тех пор пока мы не достигнем ее конца, что определено пробелом
или другим разделителем. Так что мы должны хранить строку достаточно долго для того,
чтобы выяснить имеем мы ключевое слово или нет. Это ограниченная форма перебора с
возвратом.
Поэтому структура стандартного компилятора включает разбиение функций
низкоуровневого и высокоуровневого синтаксического анализа. Лексический анализатор
работает на символьном уровне собирая символы в строки и т.п., и передавая их
синтаксическому анализатору как неделимые лексемы. Также считается нормальным
позволить сканеру выполнять работу по идентификации ключевых слов.
КОНЕЧНЫЕ АВТОМАТЫ И АЛЬТЕРНАТИВЫ
Я упомянул, что регулярные выражения могут анализироваться с использованием
конечного автомата. В большинстве книг по компиляторам а также в большинстве
компиляторов, вы обнаружите, что это применяется буквально. Обычно они имеют
настоящую реализацию конечного автомата с целыми чис л ам и, используемыми для
определения текущего состояния и таблицей действий, выполняемых для каждой
комбинации текущего состояния и входного символа. Если вы пишите "front end" для
компилятора, используя популярные Unix инструменты LEX и YACC, это то, что вы
получите. Выход LEX - конечый автомат, реализованный на C плюс таблица действий,
соответствующая входной грамматике данной LEX. Вывод YACC аналогичен...
исскуственный таблично-управляемый синтаксический анализатор плюс таблица,
соответствующая синтаксису языка.
Однако это не единственный вариант. В наших предыдущих главах вы много раз
видели, что возможно реализовать синтаксические анализаторы специально не имея
дела с таблицами, стеками и переменными состояния. Фактически в пятой главе я
предупредил вас, что если вы считает себя нуждающимся в этих вещах , возможно вы
делаете что-то неправильно и не используете возможности Паскаля. Существует в
основном два способа определить состояние конечного автомата: явно, с номером или
кодом состояния и неявно, просто на основании того факта, что я нахожусь в каком-то
определенном месте кода (если сегодня вторник, то это должно быть Бельгия). Ранее мы
полагались в основном на неявные методы, и я думаю вы согласитесь, что они работают
здесь хорошо.
На практике может быть даже не обязательно иметь четко определенный лексический
анализатор. Это не первый наш опыт работы с многосимвольными токенами. В третьей
главе мы расширили наш синтаксический анализатор для их поддержки и нам даже не
был нужен лексический анализатор. Причиной было то, что в узком контексте мы всегда
могли сказать просто рассматривая единственный предсказывающий символ, имеем ли
мы дело с цифрой, переменной или оператором. В действительности мы построили
распределенный лексический анализатор, используя процедуры GetName и GetNum.
Имея ключевые слов мы не можем больше знать с чем мы имеем дело до тех пор,
пока весь токен не будет прочитан. Это ведет нас к более локализованному сканеру, хотя,
как вы увидите, идея распределенного сканера все же имеет свои достоинства.