Россия |
Лексический анализ
Пример 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.