Материал: LabWork2

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Лабораторная работа №2.

 

Лексическии анализатор

 

Теоретические сведения.............................................................................................................

1

Разработка лексического анализатора ....................................................................................................

5

Генератор лексических анализаторов Flex ..........................................................................................

11

Задание на лабораторную работу.......................................................................................

18

Контрольные вопросы ..............................................................................................................

20

Список литературы .....................................................................................................................

20

Теоретические сведения

Первая фаза компиляции называется лексическим анализом или сканированием.

Лексический анализатор (сканер) читает поток символов,

составляющих исходную программу, и группирует эти символы в значащие последовательности, называющиеся лексемами.

Лексема – это структурная единица языка, которая состоит из элементарных символов языка и не содержит в своём составе других структурных единиц языка. Лексемами языков программирования являются идентификаторы, константы, ключевые слова языка, знаки операций и т.п.

На вход лексического анализатора поступает текст исходной программы, а выходная информация передаётся для дальнейшей обработки синтаксическому анализатору. Для каждой лексемы сканер строит выходной токен (англ. token – знак, символ) вида

имя_токена, значение_атрибута

Первый компонент токена, имя_токена, представляет собой абстрактный символ, использующийся во время синтаксического анализа, а второй компонент, значение атрибута, указывает на запись в таблице идентификаторов, соответствующую данному токену.

Предположим, например, что исходная программа содержит инструкцию присваивания a = b + c * d

Символы в этом присваивании могут быть сгруппированы в следующие лексемы и отображены в следующие токены:

1)a представляет собой лексему, которая может отображаться в токен id, 1, где id – абстрактный символ, обозначающий идентификатор, а 1 указывает запись в таблице идентификаторов для a, в которой хранится такая информация как имя и тип идентификатора;

2)cимвол присваивания = представляет собой лексему, которая отображается в токен =. Поскольку этот токен не требует значения атрибута, второй компонент данного токена

1

опущен. В качестве имени токена может быть использован любой абстрактный символ, например, такой, как «assign», но для удобства записи в качестве имени абстрактного символа можно использовать саму лексему;

3)b представляет собой лексему, которая отображается в токен id, 2, где 2 указывает на запись в таблице идентификаторов для b;

4)+ является лексемой, отображаемой в токен +;

5)c – лексема, отображаемая в токен id, 3, где 3 указывает на запись в таблице идентификаторов для c;

6)* – лексема, отображаемая в токен *;

7)d – лексема, отображаемая в токен id, 4, где 4 указывает на запись в таблице идентификаторов для d.

Пробелы, разделяющие лексемы, лексическим анализатором пропускаются.

Представление инструкции присваивания после лексического анализа в виде последовательности токенов примет следующий вид:

id, 1›‹=›‹id, 2›‹+›‹*›‹id, 3›‹id, 4.

В таблице 1 приведены некоторые типичные токены, неформальное описание их шаблонов и некоторые примеры лексем. Шаблон (англ. pattern) – это описание вида, который может принимать лексема токена. В случае ключевого слова шаблон представляет собой просто последовательность символов, образующих это ключевое слово.

Чтобы увидеть использование этих концепций на практике, рассмотрим инструкцию на языке программирования Си

printf("Total = %d\n", score);

в которой printf и score представляют собой лексемы, соответствующие токену id, a "Total = %d\n" является лексемой, соответствующей токену literal.

Таблица 1. Примеры токенов

Токен

Неформальное описание

Примеры лексем

if

Символы i, f

if

else

Символы e, l, s, e

else

comp

< или > или <= или >= или == или !=

<=

id

Буква, за которой следуют буквы и цифры

score, D2

number

Любая числовая константа

3.14159

literal

Последовательность любых символов, заключённая в

“Total = %d\n”

 

кавычки (кроме самих кавычек)

 

С теоретической точки зрения лексический анализатор не является обязательной, необходимой частью компилятора. Его функции могут выполняться на этапе синтаксического разбора. Однако существует несколько причин, исходя из которых в состав практически всех компиляторов включают лексический анализ:

упрощается работа с текстом исходной программы на этапе синтаксического разбора и сокращается объём обрабатываемой информации, так как лексический анализатор

2

структурирует поступающий на вход исходный текст программы и удаляет всю незначащую информацию;

для выделения в тексте и разбора лексем можно применять простую, эффективную и теоретически хорошо проработанную технику анализа, в то время как на этапе синтаксического анализа конструкций исходного языка используются достаточно сложные алгоритмы разбора;

сканер отделяет сложный по конструкции синтаксический анализатор от работы непосредственно с текстом исходной программы, структура которого может

варьироваться в зависимости от версии входного языка – при такой конструкции компилятора при переходе от одной версии языка к другой достаточно только перестроить относительно простой сканер.

Функции, выполняемые лексическим анализатором, и состав лексем, которые он выделяет в тексте исходной программы, могут меняться в зависимости от версии компилятора. В основном лексические анализаторы выполняют исключение из текста исходной программы комментариев и незначащих пробелов, а также выделение лексем следующих типов: идентификаторов, строковых, символьных и числовых констант, ключевых (служебных) слов входного языка.

В большинстве компиляторов лексический и синтаксический анализаторы – это взаимосвязанные части. Лексический разбор исходного текста в таком варианте выполняется поэтапно, так что синтаксический анализатор, выполнив разбор очередной конструкции языка, обращается к сканеру за следующей лексемой. При этом он может сообщить информацию о том, какую лексему следует ожидать. В процессе разбора может даже происходить «откат назад», чтобы выполнить анализ текста на другой основе. В дальнейшем будем исходить из предположения, что все лексемы могут быть однозначно выделены сканером на этапе лексического разбора.

Работу синтаксического и лексического анализаторов можно изобразить в виде схемы, приведённой на рисунке 1.

 

 

 

 

 

Токен

 

 

 

Исходная

 

 

Лексический

Синтаксический

 

К семантическому

 

 

 

 

 

 

 

программа

анализатор

 

 

 

анализатор

 

анализатору

 

Запрос

 

 

 

 

 

 

 

 

 

 

 

 

 

следующего

 

 

 

 

 

 

 

токена

 

 

Таблица

идентификаторов

Рисунок 1. Взаимодействие лексического анализатора с синтаксическим

В простейшем случае фазы лексического и синтаксического анализа могут выполняться компилятором последовательно. Но для многих языков программирования информации на этапе лексического анализа может быть недостаточно для однозначного определения типа и границ очередной лексемы.

Иллюстрацией такого случая может служить пример оператора присваивания из языка программирования Си

k = i+++++j;

который имеет только одну верную интерпретацию (если операции разделить пробелами):

3

k = i++ + ++j;

Если невозможно определить границы лексем, то лексический анализ исходного текста должен выполняться поэтапно. Тогда лексический и синтаксический анализаторы должны функционировать параллельно. Лексический анализатор, найдя очередную лексему, передаёт её синтаксическому анализатору, тот пытается выполнить анализ считанной части исходной программы и может либо запросить у лексического анализатора следующую лексему, либо потребовать от него вернуться на несколько шагов назад и попробовать выделить лексемы с другими границами.

Очевидно, что параллельная работа лексического и синтаксического анализаторов более сложна в реализации, чем их последовательное выполнение. Кроме того, такая реализация требует больше вычислительных ресурсов и в общем случае большего времени на анализ исходной программы.

Чтобы избежать параллельной работы лексического и синтаксического анализаторов, разработчики компиляторов и языков программирования часто идут на разумные ограничения синтаксиса входного языка. Например, для языка Си принято соглашение, что при возникновении проблем с определением границ лексемы всегда выбирается лексема максимально возможной длины. Для рассмотренного выше оператора присваивания это приводит к тому, что при чтении четвёртого знака + из двух вариантов лексем (+ – знак сложения, ++ – оператор инкремента) лексический анализатор выбирает самую длинную, т.е. ++, и в целом весь оператор будет разобран как

k = i++ ++ +j;

что неверно. Компилятор gcc в этом случае выдаст сообщение об ошибке: lvalue required as increment operand – в качестве операнда оператора инкремента требуется l-значение. Любые неоднозначности при анализе данного оператора присваивания могут быть исключены только в случае правильной расстановки пробелов в исходной программе.

Вид представления информации после выполнения лексического анализа целиком зависит от конструкции компилятора. Но в общем виде её можно представить как таблицу лексем, которая в каждой строчке должна содержать информацию о виде лексемы, её типе и, возможно, значении. Обычно такая таблица имеет два столбца: первый – строка лексемы, второй

– указатель на информацию о лексеме, может быть включён и третий столбец – тип лексем. Не

следует путать таблицу лексем и таблицу идентификаторов – это две принципиально разные таблицы! Таблица лексем содержит весь текст исходной программы, обработанный лексическим анализатором. В неё входят все возможные типы лексем, при этом, любая лексема может в ней встречаться любое число раз. Таблица идентификаторов содержит только следующие типы лексем: идентификаторы и константы. В неё не попадают ключевые слова входного языка, знаки операций и разделители. Каждая лексема в таблице идентификаторов может встречаться только один раз.

Вот пример фрагмента текста программы на языке Паскаль и соответствующей ему таблицы лексем:

...

begin

for i:=1 to N do fg := fg * 0.5

...

4

Таблица 2. Таблица лексем программы

Лексема

Тип лексемы

Значение

begin

Ключевое слово

X1

for

Ключевое слово

X2

i

Идентификатор

i : 1

:=

Знак присваивания

 

1

Целочисленная константа

1

to

Ключевое слово

X3

N

Идентификатор

N : 2

do

Ключевое слово

X4

fg

Идентификатор

fg : 3

:=

Знак присваивания

 

fg

Идентификатор

fg : 3

*

Знак арифметической операции

 

0.5

Вещественная константа

0.5

В таблице 2 поле «значение» подразумевает некое кодовое значение, которое будет помещено в итоговую таблицу лексем. Значения, приведённые в примере, являются условными. Конкретные коды выбираются разработчиками при реализации компилятора. Связь между таблицей лексем и таблицей идентификаторов отражена в примере некоторым индексом, следующим после идентификатора за знаком «:». В реальном компиляторе эта связь определяется его реализацией.

Разработка лексического анализатора

В качестве примера возьмем входной язык, содержащий операторы цикла for (…; …; …) do …, разделённые символом ; (точка с запятой). Операторы цикла содержат идентификаторы, знаки сравнения <, >, =, десятичные числа с плавающей точкой (в обычной и экспоненциальной форме), знак присваивания (:=).

Описанный выше входной язык может быть задан с помощью КС-грамматики G ({for, do,

‘:=’, ‘<’, ‘>’, ‘=’, ‘-’, ‘+’, ‘(‘, ‘)’, ‘;’, ‘.’, ‘_’, ‘a’, ‘b’, ‘c’, ..., ‘x’, ‘y’, ‘z’, ‘A’, ‘B’, ‘C’, ..., ‘X’, ‘Y’, ‘Z’, ‘0’, ‘1’, ‘2’, ’3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’}, {S, A, B, C, I, L, N, Z}, P, S) с правилами Р (правила представлены в расширенной форме Бэкуса-Наура):

S for (A; A; A;) do A; | for (A; A; A;) do S; | for (A; A; A;) do A; S

A I := B

B C > C | C < C | C = C

C I | N

I (_ | L) {_ | L | Z | 0}

N [- | +] ({0 | Z} . {0 | Z} | {0 | Z}. | {0 | Z}) [(e | E) [- | +]{0 | Z}][f | l | F | L]

L a | b | c | … | x | y | z | A | B | C | … | X | Y | Z

Z 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Целевым символом грамматики является символ S. Лексемы входного языка разделим на несколько классов:

ключевые слова языка (for, do) – класс 1;

разделители и знаки операций (‘(‘, ‘)’, ‘;’, '<', ‘>’, ‘=’) – класс 2;

знак операции присваивания (‘:=’) – класс 3;

идентификаторы – класс 4;

5

Источник: https://studfile.net/preview/16527600/