Под лексическим анализом будем понимать процесс предварительной обработки исходной программы, в котором основные лексические единицы программы —лексемы: ключевые (служебные) слова, идентификаторы, метки, константы приводятся к единому формату и заменяются условными кодами или ссылками на соответствующие таблицы, а комментарии исключаются из текста программы.
Выходами лексического анализа являются поток образов лексем-дескрипторов и таблицы; в последних хранятся значения выделенных в программе лексем.
Дескриптор — это пара вида: (<тип лексемы>, <указатель>),
где <тип лексемы> — это, как правило, числовой код класса лексемы, который означает, что лексема принадлежит одному из конечного множества классов слов, выделенных в языке программирования;
<указатель> — это может быть либо начальный адрес области основной памяти, в которой хранится адрес этой лексемы, либо число, адресующее элемент таблицы, в которой хранится значение этой лексемы.
Количество классов лексем (т.е. различных видов слов) в языках программирования может быть различным. Наиболее распространенными классами являются:
· идентификаторы;
· служебные (ключевые) слова;
· разделители;
· константы.
Могут вводиться и другие классы. Это обусловлено, в первую очередь, той ролью, которую играют различные виды слов при написании исходной программы и, соответственно, при переводе ее в машинную программу. При этом наиболее предпочтительным является разбиение всего множества слов, допускаемого в языке программирования, на такие классы, которые бы не пересекались между собой. В этом случае лексический анализ можно выполнить более эффективно. В общем случае все выделяемые классы являются либо конечными (ключевые слова, разделители и др.) — классы фиксированных для данного языка программирования слов, либо бесконечными или очень большими (идентификаторы, константы, метки) — классы переменных для данного языка программирования слов.
С этих позиций коды образов лексем (дескрипторов) из конечных классов всегда одни и те же в различных программах для данного компилятора. Коды же образов лексем из бесконечных классов различны для разных программ и формируются каждый раз на этапе лексического анализа.
В ходе лексического анализа значения лексем из бесконечных классов помещаются в таблицы соответствующих классов. Конечность таблиц объясняет ограничения, существующие в языках программирования на длины (и соответственно число) используемых в программе идентификаторов и констант. Необходимо отметить, что числовые константы перед помещением их в таблицу могут переводиться из внешнего символьного во внутреннее машинное представление. Содержимое таблиц, в особенности таблицы идентификаторов, в дальнейшем пополняется на этапе семантического анализа исходной программы и используется на этапе генерации объектной программы.
Рассмотрим основные идеи, которые лежат в основе построения лексического анализатора, проблемы, возникающие при его работе, и подходы к их устранению.
Первоначально в тексте исходной программы лексический анализатор выделяет последовательность символов, которая по его предположению должна быть словом в программе, т.е. лексемой. Может выделяться не вся последовательность, а только один символ, который считается началом лексемы. Это наиболее ответственная часть работы лексического анализатора. Более просто она реализуется для тех языков программирования, в которых слова в программе отделяются друг от друга специальными разделителями (например, пробелами), либо запрещается использование служебных слов в качестве переменных, либо классы лексем опознаются по вхождению первого (последнего) символа лексемы.
После этого (или параллельно с этим) проводится идентификация лексемы. Она заключается в сборке лексемы из символов, начиная с выделенного на предыдущем этапе, и проверки правильности записи лексемы данного класса.
Идентификация лексемы из конечного класса выполняется путем сравнения ее с эталонным значением. Основная проблема здесь — минимизация времени поиска эталона. В общем случае может понадобиться полный перебор слов данного класса, в особенности для случая, когда выделенное для опознания слово содержит ошибку. Уменьшить время поиска можно, используя различные методы ускоренного поиска:
· метод линейного списка;
· метод упорядоченного списка;
· метод расстановки и другие.
Для идентификации лексем из бесконечных (очень больших) классов используются специальные методы сборки лексем с одновременной проверкой правильности написания лексемы. При построении этих алгоритмов широко
применяется формальный математический аппарат — теория регулярных языков, грамматик и конечных распознавателей. Практический аспект этой теории для целей лексического анализа будет рассмотрен в п. 2.2.
При успешной идентификации значение лексемы из бесконечного класса помещается в таблицу идентификации лексем данного класса. При этом необходимо предварительно проверить: не хранится ли там уже значение данной лексемы, т.е. необходимо проводить просмотр элементов таблицы. Если ее там нет, то значение помещается в таблицу. При этом таблица должна допускать расширение. Опять же для уменьшения времени доступа к элементам таблицы она должна быть специальным образом организована, при этом должны использоваться специальные методы ускоренного поиска элементов.
После проведения успешной идентификации лексемы формируется ее образ — дескриптор, он помещается в выходной поток лексического анализатора. В случае неуспешной идентификации формируются сообщения об ошибках в написании слов программы.
В ходе лексического анализа могут выполняться и другие виды лексического контроля, в частности, проверяется парность скобок и других парных символов.
Выходной поток с лексического анализатора в дальнейшем поступает на вход синтаксического анализатора. Имеется две возможности их связи:
раздельная связь, при которой выход лексического анализатора формируется полностью и затем передается синтаксическому анализатору;
нераздельная связь, когда синтаксическому анализатору требуется очередной образ лексемы, он вызывает лексический анализатор, который генерирует требуемый дескриптор и возвращает управление синтаксическому анализатору.
Второй вариант характерен для однопроходных трансляторов. Таким образом, процесс лексического анализа может быть достаточно простым, но в смысле времени компиляции оказывается довольно долгим. больше половины времени, затрачиваемого компилятором на компиляцию, приходится на этап лексического анализа. Ниже приведен пример программы и показан возможный вариант содержимого таблиц и выхода лексического анализатора. При этом в качестве кодов типов лексем используются числа:
10 — ключевое слово;
20 — разделитель;
30 — идентификатор;
40 — константа.
Вход лексического анализатора:
main()
{
int x,y,z;
x=5;
y=5;
z=x+y;
}
Внутренние таблицы лексического анализатора:
Таблица ключевых слов (код 10)
номер п/п |
Ключевое слово |
1 2 3 4 … |
main int float for … |
Таблица разделителей (код 20)
номер п/п |
Разделитель |
1 2 3 4 5 6 7 8 9 10 … |
; { } , = ( ) . + - … |
Выход лексического анализатора:
Таблица идентификаторов (код 30)
номер п/п |
Идентификатор |
1 2 3 |
X Y Z |
Таблица констант (код 40)
№ п/п |
Значение константы |
1 |
5 |
Дескрипторный текст:
(10,1) (20,6) (20,7)
(20,2)
(10,2) (30,1) (20,4) (30,2) (20,4) (30,3) (20,1)
(30,1) (20,5) (40,1) (20,1)
(30,2) (20,5) (40,1) (20,1)
(30,3) (20,5) (30,1) (20,9) (30,2) (20,1)
(20,3).