- •1. Введение
- •2. Регулярные выражения в Lex-правилах
- •2.1. Обозначения символов в выражениях
- •2.2. Операторы регулярных выражений
- •2.3. Оператор выделения классов символов
- •2.4. Повторители
- •2.5. Операторы выбора
- •2.6. Оператор {}
- •3. Структура Lex-программы
- •3.1. Раздел определений Lex-программы
- •3.2. Раздел правил
- •3.2.1. Действия в правилах Lex-программы
- •3.2.2. Порядок действия активных правил
- •3.3. Раздел программ пользователя
- •3.4. Комментарии Lex-программы
- •3.5. Примеры Lex-программ
- •4. Структура файла lex.Yy.C
- •5. Функция yywrap()
- •6. Функция reject
- •7. Функции yyless и yymore
- •8. Совместное использование lex и yacc
- •9. Использование Ратфора
- •10. Флаги Lex
Генератор программ лексического анализа lex
Производственно-внедренческий кооператив
"И Н Т Е Р Ф Е Й С"
Диалоговая Единая Мобильная
Операционная Система
Демос/P 2.1
Генератор программ лексического анализа lex
Москва
1988
Аннотация
В документе описан язык программирования lex, предназ-
наченный для разработки программ лексического анализа. При-
водятся правила работы с компилятором языка lex ОС ДЕМОС.
1. Введение
lex - генератор программ лексического анализа. Лекси-
ческий анализ - это распознавание лексем во входном потоке
символов. Предположим, что задано некоторое конечное мно-
жество слов (лексем) в некотором языке и некоторое входное
слово. Необходимо установить, какой элемент множества (если
он существует) совпадает с данным входным словом.
Обычно лексический анализ выполняется так называемым
лексическим анализатором. Лексический анализатор - это
программа.
Лексический анализ применяется во многих случаях, нап-
ример, для построения пакетного редактора или в качестве
распознавателя директив в диалоговой программе и т.д.
Однако, наиболее важное применение лексического анализатора
- это использование его в компиляторе. Здесь лексический
анализатор выполняет функцию программы ввода данных.
Лексический анализатор выполняет первую стадию компиля-
ции - читает строки компилируемой программы, выделяет лек-
семы и передает их на дальнейшие стадии компиляции (грамма-
тический разбор, кодогенерацию и т.д.).
Лексический анализатор распознает тип каждой лексемы и
соответствующим образом помечает ее. Например, при компиля-
ции Си-программы могут быть выделены следующие типы лексем:
число, идентификатор, оператор, ограничитель и т.д.
Лексический анализатор должен не только выделить лек-
сему, но и выполнить некоторые преобразования. Например,
если лексема - число, то его необходимо перевести во внут-
реннюю (двоичную) форму записи как число с плавающей или
фиксированной точкой. А если лексема - идентификатор, то
его необходимо разместить в таблице, чтобы в дальнейшем
обращаться к нему не по имени, а по адресу в таблице.
Хотя лексический анализ по своей идее прост, тем не
менее эта фаза работы компилятора часто занимает больше вре-
мени, чем любая другая. Частично это происходит из-за необ-
ходимости просматривать и анализировать исходный текст сим-
вол за символом. Иногда даже бывает необходимо вернуть про-
читанный символ во входной поток с тем, чтобы повторить
просмотр и анализ.
Происходит это потому, что часто бывает трудно опреде-
лить, где проходят границы лексемы.
Допустим, имеются две лексемы:
make
makefile
3
Пусть из входного потока поступает набор символов:
...makefile...
При анализе входного потока символов будет выделена лексема
make, хотя правильно было бы выделить лексему makefile.
Единственный способ преодолеть это затруднение - прос-
мотр полученной цепочки символов назад и вперед. В нашем
примере при выделении лексемы make мы должны просмотреть
следующий поступающий символ и, если он будет символом "f",
то вполне возможно, что поступает лексема makefile.
Процесс просмотра входного потока можно рассматривать
как движение влево и вправо рамки над цепочкой символов. При
этом анализируется только тот символ, который охвачен рам-
кой.
...
. .
source make.f.ile file compiler
. .
...
<=== ===>
Анализ заключается в определении соответствия рассматривае-
мой последовательности символов некоторому так называемому
регулярному выражению.
Например, регулярное выражение
(+?[0-9])+|(-?[0-9])+
позволяет выделить в цепочке все лексемы типа целое, перед
которыми либо указан знак (+ или -), либо не указан. Для
чисел с точкой это выражение имело бы вид:
(+?[0-9.])+|(-?[0-9.])+
В тех случаях, когда выделение лексемы затруднено либо по
причине того, что одно регулярное выражение не позволяет ее
однозначно определить, либо из-за того, что лексема является
частью другой, приходится прибегать к контекстно-зависимым
алгоритмам анализа с использованием левого и правого направ-
лений просмотра входной цепочки символов.
lex частично или полностью автоматизирует процесс
написания программы лексического анализа. lex - это програм-
мирующая программа или генератор программ. lex строит прог-
рамму - лексический анализатор на так называемом host-языке
(или "главном" языке). Это значит, что Lex-программа пишется
на "языке" lex, а Lex-генератор, в свою очередь, генерирует
программу лексического анализа на каком-либо другом языке.
4
Данная версия lex генерирует лексические анализаторы на язы-
ках Си и Ратфор (рациональный диалект Фортрана). В качестве
host-языка мы будем использовать язык Си. Сведения об
использовании в качестве host-языка Ратфор выделены в
отдельный параграф.
В каталоге /usr/lib/lex имеется файл-заготовка ncform,
который используется Lex-генератором для построения лекси-
ческого анализатора. Этот файл является уже готовой прог-
раммой лексического анализа, но в нем не определены дейст-
вия, которые необходимо выполнить при распознавании лексемы,
отстутствуют и сами лексемы, не сформированы рабочие массивы
и т.д.
lex на основе Lex-программы достраивает файл ncform. В
результате мы получаем файл со стандартным именем lex.yy.c,
который является текстом Си-программы, осуществляющей лекси-
ческий анализ.
Lex-программа имеет следующий формат:
определения
%%
правила
%%
подпрограммы, составленные
пользователем
Любой из этих разделов может быть пустым. Простейшая Lex-
программа имеет вид:
%%
Здесь нет никаких определений и никаких правил.
Все разделы Lex-программы мы подробно рассмотрим ниже.
Сейчас целесообразно рассмотреть, что представляют собой
правила.
Правило состоит из двух частей:
РЕГУЛЯРНОЕ_ВЫРАЖЕНИЕ ДЕЙСТВИЕ
По регулярным выражениям, содержащимся в левой части правил,
lex строит детерминированный конечный автомат. Этот автомат
осуществляет интерпретацию, а не компиляцию. Количество пра-
вил и их сложность не влияют на скорость лексического ана-
лиза, если только правила не требуют слишком большого объема
повторных просмотров входной последовательности символов.
Однако, с ростом числа правил и их сложности растет размер
конечного автомата, интерпретирующего их и, следовательно,
растет размер Си-программы, реализующей этот конечный авто-
мат.
5
Рассмотрим в качестве примера следующую Lex-программу:
%%
[jJ][aA][nN][uU][aA][rR][yY] {
printf("Январь"); }
[fF][eE][bB][rR][uU][aA][rR][yY] {
printf("Февраль"); }
[mM][aA][rR][cC][hH] {
printf("Март"); }
[aA][pP][rR][iI][lL] {
printf("Апрель"); }
[mM][aA][yY] {
printf("Май"); }
[jJ][uU][nN][eE] {
printf("Июнь"); }
[jJ][uU][lL][yY] {
printf("Июль"); }
[aA][uU][gG][uU][sS][tT] {
printf("Август"); }
[sS][eE][pP][tT][eE][mM][bB][eE][rR] {
printf("Сентябрь"); }
[oO][cC][tT][oO][bB][eE][rR] {
printf("Октябрь"); }
[nN][oO][vV][eE][mM][bB][eE][rR] {
printf("Ноябрь"); }
[dD][eE][cC][eE][mM][bB][eE][rR] {
printf("Декабрь"); }
[mM][oO][nN][dD][aA][yY] {
printf("Понедельник");}
[tT][uU][eE][sS][dD][aA][yY] {
printf("Вторник"); }
[wW][eE][dD][nN][eE][sS][dD][aA][yY] {
printf("Среда"); }
[tT][hH][uU][rR][sS][dD][aA][yY] {
printf("Четверг"); }
[fF][rR][iI][dD][aA][yY] {
printf("Пятница"); }
[sS][aA][tT][uU][rR][dD][aA][yY] {
printf("Суббота"); }
[sS][uU][nN][dD][aA][yY] {
printf("Воскресенье");}
Программа строит конечный автомат, который распознает анг-
лийские наименования месяцев и дней недели. Каждое правило
здесь определеяет действие (которое взято в фигурные
скобки). Обратите внимание на то, что открывающая фигурная
скобка стоит в той же строке, что и правило - это требование
lex.
Действие в каждом правиле данной Lex-программы - это
вывод русского значения найденного английского слова. В
качестве оператора, выполняющего действие, используется биб-
лиотечная функция языка Си.
6
Пара фигурных скобок определяет блок (в смысле языка
Си), который может содержать любое количество строк. Если
действие содержит всего одну строку Си, то можно ее указать
без фигурных скобок, как обычно. Единственное условие - она
должна начинаться в той же строке, где указано регулярное
выражение.
В программе содержится только раздел правил, их всего
19. Регулярное выражение каждого правила определяет английс-
кое слово, написанное маленькими или большими латинскими
символами. Например "May" (Май) определен как
"[mM][aA][yY]". По этому регулярному выражению будет выде-
лена во входном потоке символов лексема "May", а по действию
этого правила будет выведено "Май". Наличие большой и малой
буквы в квадратных скобках обеспечивает распознавание слова
"May", написанного любыми латинскими символами.
Таким образом, данная Lex-программа строит Си-
программу, которая переводит на русский язык имена месяцев и
дней недели.
Допустим, Lex-программа размещена в файле source.l,
тогда, чтобы получить лексический анализатор на Си, необхо-
димо выполнить следующий набор команд:
% lex source.l
% cc -O lex.yy.c -ll -o program
%
lex всегда, если не указано другое, строит выходной файл с
именем lex.yy.c - Си-программу - лексический анализатор. Во
второй строке этой последовательности команд запускается
Си-компилятор, который выводит результат в файл program.
Program может работать как фильтр в конвейере команд,
как самостоятельная команда и в интерактивном режиме. Напри-
мер:
% program
May
Май
MONDAY
Понедельник
MoNdaY
Понедельник
CNTRL/C
%
Флаг -ll требует подключения библиотеки /usr/lib/libl.a -
библиотеки lex. Если необходимо получить самостоятельную
программу, как в данном случае, подключение библиотеки
7
обязательно, поскольку тогда из нее подключается головной
раздел main. В противном случае, если имеется необходимость
включить анализатор в качестве функции в другую программу
(например, в программу грамматического разбора), эту библио-
теку необходимо вызвать уже при сборке и тогда, если main
определен в вызывающей лексический анализатор программе,
редактор связей не будет подключать раздел main из библио-
теки lex.
Если необходимо получить файл с именем, отличным от
lex.yy.c, можно воспользоваться флагом -t :
% lex -t source.l >> file
По этому флагу результат поступает в файл file.