| Россия |
Лексический анализ
Пример Lex-программы
Предположим, что мы пишем анализатор, который должен уметь обрабатывать условные предложения на базе примитивных выражений, состоящих из идентификаторов и чисел. Приведем правила, задающие лексические классы для такого языка:


Кроме того, будем считать заданной структуры лексических классов и их атрибутов, которые должен порождать наш анализатор (см. след. таблицу):
| Регулярное выражение | Лексический класс (token) | Атрибут |
|---|---|---|
| ws | - | - |
| if | if_LC | - |
| then | then_LC | - |
| else | else_LC | - |
| Id | Identifier_LC | Pointer to ReprTab |
| num | Number_LC | Pointer to ReprTab |
| < | relop_LC | LT |
| <= | relop_LC | LE |
| = | relop_LC | EQ |
| < > | relop_LC | NE |
| > | relop_LC | GT |
| >= | relop_LC | GE |
Напишем соответствующую Lex-программу:
%{
определение констант
%}
%%
/* регулярные определения */
delim {\t\n}
ws {delim}+
letter {A-Za-z}
digit {0-9}
id {letter}({letter}|{digit})*
number {digit}+(\.{digit}+)?(E[+|-]?{digit}+)?
%%
{ws} {}
if {return (if_LC);}
then {return (then_LC);}
else {return (else_LC);}
{id} {yylval = addToReprTab (); return (Identifier_LC);}
{number} {yylval = addToReprTab (); return (Number_LC);}
"<" {yylval = LT; return (relop_LC);}
...Другие возможности Lex'а
Рассмотрим еще один пример - подсчет числа слов и строк в файле:
/***************** Раздел определений *********************/
NODELIM [^" "\t\n]
/* NODELIM означает любой символ, кроме разделителей слов */
int l, w, c; /* Число строк, слов, символов */
%% /******************** Раздел правил ***********************/
{ l=w=c=0; /* Инициализация */ }
{NODELIM}+ { w++; c+=yyleng; /* Слово */ }
\n { l++; /* Перевод строки */ }
. { c++; /* Остальные символы */ }
%% /******************* Раздел программ **********************/
int main()
{ yylex(); }
yywrap()
{
printf( " Lines - %d Words - %d Chars - %d\n", l, w, c );
return( 1 );
}Внутри действий в правилах можно использовать некоторые специальные конструкции и функции Lex'а:
yytext - указатель на отождествленную цепочку символов, оканчивающуюся
нулем;
yyleng - длина этой цепочки
yyless(n) - вернуть последние n символов цепочки обратно во входной поток;
yymore() - считать следующие символы в буфер yytext после текущей цепочки
yyunput(c) - поместить байт c во входной поток
ECHO - копировать текущую цепочку в yyout
yylval - еще одно возвращаемое значениеЗаглядывание вперед при лексическом анализе
Отметим, что Lex всегда работает детерминированным образом, так как не содержит возвратов к уже рассмотренным символам и всегда выдает наиболее длинную подходящую строку. Однако иногда для корректного выполнения лексического анализа необходимо производить заглядывание вперед. Например, при лексическом анализе программ на C# после прочтения символа > необходимо прочитать и последующие символы, т.к. лексема может оказаться одной из следующих: >, >=, >>, >>=, >>>, >>>=.
В некоторых случаях, заглядывание вперед еще более критично. Вернемся к рассматривавшемуся выше примеру на Фортране:
DO 5 I=1.25 DO 5 I=1,25
Поскольку в Фортране пробелы не являются значащими литерами вне комментариев и строк, то предположим, что все пробелы удаляются до начала лексического анализа. Тогда на вход лексического анализатора попадет следующее:
DO5I=1.25 DO5I=1,25
Для выделения лексем в этой ситуации мы можем использовать выражение вида r1/r2, где r1 и r2 - произвольные регулярные выражения. С использованием этого мы можем написать Lex спецификацию, которая выделяет ключевое слово DO:
DO/ ({letter} | {digit})* = ({letter} | {digit})*,При такой спецификации лексический анализатор будет заглядывать вперед пока не просканирует регулярное выражение, написанное после /. Однако, только литеры D и O будут выделенной лексемой. После удачного выделения yytext будет указывать на D и yyleng=2.