- •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
8. Совместное использование lex и yacc
yacc требует указание лексическому анализатору имени
yylex(). Именно поэтому эта функция так называется в lex.
Известно, что yacc строит выходной файл y.tab.c .
Основной в файле y.tab.c является функция yyparse, реализую-
щая алгоритм грамматического разбора. Функция yyparse содер-
жит многократное обращение к функции лексического анализа
yylex.
Для обеспечения корректной работы грамматического ана-
лизатора функция yylex должна быть согласована с конкретной
спецификацией грамматики и удовлетворять определенным требо-
ваниям.
Пользователь при описании грамматики решает, какие
конструкции целесообразнее непосредственно выделять из вход-
ного текста на этапе лексического анализа.
Сложность лексического анализатора зависит от того,
какие структурные единицы взяты за основу при описании грам-
матических правил. Детализовав грамматику до отдельных сим-
волов, можно обойтись простейшим лексическим анализатором.
Однако, в этом случае число правил растет, а грамматический
разбор оказывается менее эффективным. Поэтому пользователь
обычно должен найти некоторый компромисс при выборе набора
лексем.
Заметим, что ключевые слова описываемого входного языка
часто бывает удобно считать лексемами. Имена лексем могут
совпадать с этими ключевыми словами, недопустимым является
лишь совпадение имен лексем с зарезервированными словами
языка Си.
Основная задача функции yylex состоит во вводе из вход-
ного потока ряда очередных символов до выявления конструк-
ции, соответствующей одной из лексем, и возвращении номера
32
типа этой лексемы и, когда это необходимо, значения этой
лексемы.
Все виды лексем, кроме литералов, обозначаются некото-
рыми именами и под этими именами фигурируют в Yacc-
программе, где объявление имен лексем осуществляется дирек-
тивой token:
%token <список имен лексем>
Благодаря объявлению имен лексем в директиве token, yacc
отличает имена лексем от имен нетерминальных символов.
Пример объявления имен лексем в Yacc-программе:
%token IDENT CONST ЗНАК IF THEN GOTO
При первом появлении лексемы или литерала в секции объявле-
ний Yacc-программы за каждым из них может следовать неотри-
цательное целое число, рассматриваемое как номер_типа лек-
семы.
По умолчанию номера типов всех лексем определяются yacc
следующим образом:
- для литерала номером типа лексемы считается числовое
значение данного литерального символа, рассматривае-
мого как однобайтовое целое число;
- лексемы, обозначенные именами, в соответствии с оче-
редностью их объявления получают последовательные
номера, начиная с 257.
Для каждого имени лексемы независимо от того, переопре-
делен ли ее номер пользователем, yacc генерирует в выходном
файле y.tab.c оператор препроцессора:
#define <имя_лексемы> <номер_типа>
Значение, возвращаемое функцией yylex, является номером типа
лексемы. Таким образом, список лексем и номера их типов ука-
зываются в Yacc-программе, а определения этих лексем в Lex-
программе. Возникает проблема соответствия номеров типов
лексем в файлах y.tab.c и lex.yy.c, котороя разрешается сле-
дующим образом:
- при вызове yacc с флагом -d последовательность опера-
торов #define помещается в файл y.tab.h.;
- этот файл посредством оператора #include включается в
Lex-программу.
33
В процедуре лексического анализа кроме выделения лексем
можно предусмотреть некоторую обработку лексем определенных
типов, в частности, запоминание конкретных значений лексем.
Примером значения лексемы могут служить числовое значе-
ние символа - цифры, вычисленное значение константы, адрес
идентификатора в таблице имен (построение таблицы имен осу-
ществляет lex). Кроме того, эти значения обычно требуется
передать грамматическому анализатору. С этой целью нужное
значение должно быть присвоено внешней переменной целого
типа с именем yylval. Если функция yylex находится в отдель-
ном файле, то эта переменная должна быть объявлена:
extern int yylval;
Уточним, что значением_лексемы будем называть значение,
присвоенное при ее распознавании переменной yylval. Заметим,
что в yylval всегда должно находится значение последней
выделенной лексемы.
Допустим, мы располагаем Yacc-программой в файле
source.y и Lex-программой в файле source.l, которые необхо-
димо собрать в работающую программу. Существует два способа
сборки:
- сборка Lex- и Yacc-программы с созданием файла
y.tab.h;
- сборка Lex- и Yacc-программы без создания файла
y.tab.h.
Рассмотрим первый способ сборки.
Ниже приведен пример makefile для программы make, кото-
рая осуществляет последовательную обработку и сборку эих
программ и размещает результат в файле program:
34
program: y.tab.o lex.yy.o
cc y.tab.o lex.yy.o -ly -ll -o program
y.tab.o: y.tab.c
cc -c -O y.tab.c
lex.yy.o: lex.yy.c y.tab.h
cc -c -O lex.yy.c
y.tab.h:
y.tab.c: source.y
yacc -d source.y
lex.yy.c: source.l
lex -v source.l
clear:
rm -f yy.tab.? lex.yy.? program
В файле source.l размещена Yacc-программа, реализующая
небольшой настольный калькулятор. Калькулятор имеет 52
регистра, помеченных буквами от A до z, и разрешает исполь-
зовать арифметические выражения, содержащие операции +, -,
*, /, % (остаток от деления), & (побитовое и), | (побитовое
или) и присваивание. Как и в Си, целые числа, начинающиеся с
0, считаются восьмеричными, все остальные - десятичными.
Результат всегда выводится десятичными числами.
Калькулятор работает в интерактивном режиме с построч-
ным формированием выхода, может читать задание из файла и
выводить результат в файл.
Знак "=" используется для присваивания, а для выведения
результата достаточно нажать клавишу <ВК>. Распознаются ско-
бочные структуры, изменяющие порядок приоритетов при вычис-
лениях. Калькулятор работает только с целыми типа integer.
35
%token DIGIT LETTER
%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS
%{
int base, regs[26];
%}
%%
list:
|
list stat '\n'
|list stat error '\n' { yyerrok; }
stat:
expr { printf( "%d\n",$1 ); }
|LETTER '=' expr { regs[$1]=$3; }
expr:
'(' expr ')' { $$=$2; }
|expr '+' expr { $$=$1+$3; }
|expr '-' expr { $$=$1-$3; }
|expr '*' expr { $$=$1*$3; }
|expr '/' expr { $$=$1/$3; }
|expr '%' expr { $$=$1%$3; }
|expr '&' expr { $$=$1&$3; }
|expr '|' expr { $$=$1|$3; }
|'-' expr %prec UMINUS { $$= -$2; }
| LETTER { $$=regs[$1]; }
| number;
number:
DIGIT { $$=$1;
base=10;
if($1==0) base=8; }
|number DIGIT { $$=base*$1+$2; }
В файле source.l размещена Lex-программа лексичского
анализатора для этого калькулятора:
36
%{
#include "y.tab.h"
extern int yylval;
%}
%%
^\n ;
[ \t]* ;
[A-Za-z] {
yylval = yytext[yyleng-1] - 'a';
return(LETTER);}
[0-9] {
yylval = yytext[yyleng-1] - '0';
return(DIGIT);}
Рассмотрим второй способ сборки. Makefile теперь
существенно проще:
program: y.tab.c lex.yy.c
cc -O y.tab.c -ly -ll -o program
y.tab.c: source.y
yacc source.y
lex.yy.c: source.l
lex -v source.l
clear:
rm -f y.tab.? lex.yy.? program
Но в файлах source.y и source.l произойдут следующие измене-
ния. В разделе входной информации для Yacc-программы необхо-
димо указать строку #include lex.yy.c, а из Lex-программы
необходимо убрать строку #include "y.tab.h". Теперь файл
source.y выглядит следующим образом:
37
%token DIGIT LETTER
%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS
%{
#include "lex.yy.c"
int base, regs[26];
%}
%%
list:
|
list stat '\n'
|list stat error '\n' { yyerrok; }
stat:
expr { printf( "%d\n",$1 ); }
|LETTER '=' expr { regs[$1]=$3; }
expr:
'(' expr ')' { $$=$2; }
|expr '+' expr { $$=$1+$3; }
|expr '-' expr { $$=$1-$3; }
|expr '*' expr { $$=$1*$3; }
|expr '/' expr { $$=$1/$3; }
|expr '%' expr { $$=$1%$3; }
|expr '&' expr { $$=$1&$3; }
|expr '|' expr { $$=$1|$3; }
|'-' expr %prec UMINUS { $$= -$2; }
| LETTER { $$=regs[$1]; }
| number;
number:
DIGIT { $$=$1;
base=10;
if($1==0) base=8; }
|number DIGIT { $$=base*$1+$2; }
А файл source.l выглядит следующим образом:
38
%{
extern int yylval;
%}
%%
^\n ;
[ \t]* ;
[A-Za-z] {
yylval = yytext[yyleng-1] - 'a';
return(LETTER);}
[0-9] {
yylval = yytext[yyleng-1] - '0';
return(DIGIT);}