ДЕЙСТВИЯ
Действием в языке awk является последовательность операторов, разделенных переводами строки или точками с запятой. Действия позволяют решать множество задач, связанных с бухгалтерскими расчетами и обработкой цепочек символов.
После того, как lex распознает цепочку, сопоставляемую с регулярным выражением, заданным в начале правила, он ищет в правой части правила действие, которое надо выполнить. Среди возможных видов действий - запись типа обнаруженной лексемы и значения, которое принимает лексема (если таковые имеются); замена одной лексемы на другую; подсчет числа вхождений лексем или типов лексем. Все, что Вы хотите проделать, нужно записать в виде фрагментов программ на языке C. Действие может состоять из произвольного числа операторов. Можно напечатать сообщение с найденным текстом или сообщение, как-то трансформирующее этот текст. Так, чтобы распознать выражение Amelia Earhart и сообщить об этом, можно специфицировать правило:
"Amelia Earhart" printf("нашли Amelia");
Чтобы заменить в тексте длинные медицинские термины их эквивалентными обозначениями, следует воспользоваться правилом
Electroencephalogram printf("EEG");
Если требуется подсчитать число строк в тексте, нужно распознавать признаки конца строк и увеличивать на единицу счетчик строк. lex использует стандартные для языка C управляющие последовательности символов, подобные \n для символа перевода строки. Для подсчета числа строк можно использовать правило
\n lineno++;
где lineno, как и другие C-переменные, описывается в секции определений, которую мы обсудим позднее.
lex сохраняет каждую сопоставленную цепочку в массиве символов yytext[]. Вы можете распечатать содержимое этого массива или манипулировать им, как угодно. Иногда Ваше действие может состоять из двух или большего числа C-операторов и Вы должны (или решили из соображений стиля и ясности) написать их на нескольких строках. Чтобы информировать lex о том, что действие относится к одному правилу, надо просто заключить C-операторы в скобки. Например, чтобы подсчитать общее число всех цепочек цифр во входном тексте, печатать текущее общее число таких цепочек и выводить каждую из них, как только она обнаружена, можно воспользоваться такой записью:
\+?[0-9]+ { digstrgcount++; printf("%d",digstrngcount); yytext [yyleng] = (char) 0; printf("%s",yytext); }
Эта спецификация сопоставляется с цепочками цифр, перед которыми, быть может, стоит знак плюс, поскольку операция ? обозначает, что знак плюс спереди не обязателен. Кроме того, будут учитываться и отрицательные цепочки цифр, так как последовательность, которая следует за знаком минус, -, будет сопоставляться с приведенным шаблоном. В следующем разделе показано, как отличить отрицательные целые числа от положительных.
С каждым грамматическим правилом пользователь может связать действия, которые будут выполняться при применении правила. Действия могут возвращать значения и получать значения, возвращенные предыдущими действиями. Кроме того, если требуется, лексический анализатор может возвращать значения лексем.
Действие - это произвольный оператор на языке C. Следовательно, в действии можно выполнять операции ввода/вывода, вызывать подпрограммы, изменять значения массивов и переменных. Действие задается одним или несколькими операторами в фигурных скобках, { и }. Например, конструкции
A : '(' B ')' { hello (1, "abc"); } ;
и
XXX : YYY ZZZ { (void) printf ("сообщение\n"); flag = 25; } ;
являются грамматическими правилами с действиями.
Для организации взаимосвязи между действиями и процедурой разбора используется знак $ (доллар). Псевдопеременная $$ представляет значение, возвращаемое завершенным действием. Например, действие
{$$ = 1;}
возвращает значение 1; в сущности, это все, что оно делает.
Чтобы получать значения, возвращаемые предыдущими действиями и лексическим анализатором, действие может использовать псевдопеременные $1, $2, ... $n. Они обозначают значения, возвращаемые компонентами от 1-го до n-го, стоящими в правой части правила; компоненты нумеруются слева направо. В случае правила
A : B C D ;
$2 принимает значение, возвращенное C, а $3 - значение, возвращенное D.
Пример с правилом
expr : '(' expr ')' ;
банален. Ожидается, что значение, возвращаемое этим правилом, равно значению expr в скобках. Поскольку первый компонент конструкции - левая скобка, требуемый результат можно получить так:
expr : '(' expr ')' { $$ = $2; } ;
По умолчанию значение правила равно значению первого компонента в нем ($1). Поэтому грамматические правила вида
A : B ;
зачастую не требуют явного указания действия. В предыдущих примерах действия всегда выполнялись в конце применения правила. Иногда же бывает желательно получить управление до того, как правило применено полностью. yacc позволяет писать действие в середине правила так же, как и в конце. Считается, что такое действие возвращает значение, доступное действиям справа от него посредством обычного механизма $. В свою очередь, оно имеет доступ к значениям, возвращаемым действиями слева от него. Таким образом, в правиле, указанном ниже, результатом действия является присваивание переменной x значения 1, а переменной y - значения, которое возвращает C.