Опубликован: 23.07.2006 | Доступ: свободный | Студентов: 2213 / 889 | Оценка: 4.28 / 4.17 | Длительность: 21:37:00
Специальности: Системный архитектор
Лекция 5:

Лексический анализ

< Лекция 4 || Лекция 5: 123456 || Лекция 6 >

Пример Lex-программы

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

\textit{id \to  letter (letter + digit)*} \textit{num \to  digit}^{+}\textit{ (.digit}^{+}\textit{+ \varepsilon ) (E('+' + '--' + \varepsilon )digit}^{+}\textit{+ \varepsilon )}

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

Регулярное выражение Лексический класс (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.

< Лекция 4 || Лекция 5: 123456 || Лекция 6 >