Секция подпрограмм – необязательная заключительная секция файла спецификации лексем. Она должна быть отделена от предшествующей секции правил разделительной парой символов процента %% и продолжается до конца файла спецификации лексем. Содержимое секции правил без изменений включается в исходный текст лексического анализатора и должно быть оформлено в нотации языка программирования C.
Секция подпрограмм в основном предназначена для спецификации исходного кода прикладных функций, которые используются в действиях секции правил. Кроме того, в секции подпрограмм можно перегрузить исходные коды некоторых стандартных функций ввода-вывода лексического анализатора, которые предоставляет генератор LEX, а также специфицировать основную функцию main, когда это необходимо. При этом следует сохранять неизменными имена, типы аргументов и кодов возврата перегружаемых функций.
На практике наиболее часто встречается перегрузка стандартной функции yywrap, которая автоматически вызывается лексическим анализатором при достижении конца входного потока стандартного ввода. Эта функция не имеет аргументов и должна возвращать целочисленное значение 0 или 1, оценка которого предусмотрена в программе лексического анализатора. При этом возврат значения 1 вызывает корректное завершение процедуры лексического анализа. Возврат нулевого значения предоставляет возможность продолжить выполнение процедуры лексического анализа для обработки данных, поступающих из другого источника. В стандартном варианте функция yywrap возвращает значение 1. Перегрузка стандартной функции yywrap обычно применяется, когда необходимо изменить ее стандартный код возврата с 1 на 0 или выполнить определенные завершающие действия в конце лексического анализа входного потока при коде возврата 1.
Использование прикладных и перегруженных функций секции подпрограмм иллюстрирует следующий пример спецификаций лексического анализатора, который отображает максимальную длину слова входного текста через поток стандартного вывода:

%%
[A-Za-z]+      wordlen(yyleng);
.              |
\n             ;
%%

/* Прикладная функция оценки длины слова */
int wordlen(int len) {
static int maxlen = 0;
if(len > maxlen)
  maxlen = len;
} /* wordlen */

/* Перегрузка стандартной функции yywrap */
int yywrap() {
printf("%d\n", wordlen(0));
} /* wordlen */

В этом примере, в соответствии с регулярным выражением первого правила, словом считается любая последовательность строчных и/или заглавных латинских букв. Для оценки длины каждого полученного слова в действии этого правила вызывается прикладная функция wordlen, которая определена в секции подпрограмм. При вызове в действии правила ей передается значение внутренней переменной yyleng, фиксирующей длину текущего слова, чтобы сопоставить его со значением статической переменной maxlen, которая сохраняет размер наиболее длинного из полученных слов входного текста. Текущее значение переменной maxlen изменяется, если его величина меньше длины очередного слова и всегда может быть получено через код возврата прикладной функции wordlen. Однако в данном примере код возврата прикладной функции wordlen используется только для контроля итогового значения статической переменной maxlen, которое получается после обработки всех слов входного потока и, следовательно, соответствует максимальной длине слова входного текста.
Конец входного потока инициирует автоматический вызов стандартной функции yywrap, исходный код которой перегружен в секции подпрограмм. Перегрузка стандартной функции yywrap осуществляется с целью обеспечить контрольный вызов прикладной функции wordlen в конце лексического анализа, когда ее код возврата определяет максимальную длину слова входного текста. При этом прикладная функция wordlen вызывается с нулевым аргументом, чтобы исключить изменение величины статической переменной maxlen, а ее код возврата передается библиотечной функции printf системы программирования C для отображения полученного результата через поток стандартного вывода. Выполнение функции yywrap оканчивается возвратом значения 1, чтобы корректно обозначить завершение процедуры лексического анализа входного потока.
Кроме стандартной функции yywrap в секции подпрограмм могут быть перегружены еще две стандартные функции input и unput, генератор LEX использует для обработки входного потока. Перегружаемые функции input и unput являются LEX ориентированными реализациями библиотечных функций getc и ungetc системы программирования C, которые конкретизированы для обработки потока стандартного ввода в лексическом анализаторе.
В частности, стандартная функция input в лексическом анализаторе обеспечивает чтение входного потока, возвращая код каждого полученного символа или 0, когда достигнут конец входного потока. Стандартная функция unput обеспечивает возврат обратно во входной поток для повторного чтения символ, код которого задается ее целочисленным аргументом.
В некоторых реализациях генератора LEX дополнительно к стандартным функциям input и unput предусмотрена также стандартная функция output, которая используется для обработки выходного потока и эквивалентна библиотечной функции putc из системы программирования C. Она обеспечивает стандартный вывод символа, код которого идентифицирован ее аргументом. Однако в большинстве современных версий генератора LEX эта стандартная функция либо отсутствует, либо недоступна для явного обращения.
Следует отметить, что перегрузка стандартных функций ввода-вывода относительно редко применяется на практике. Обычно они неявно вызываются лексическим анализатором для стандартной обработки входного потока. Однако в некоторых случаях целесообразно реализовать явный вызов этих функций в действиях правил или в прикладных функциях секции подпрограмм.
Эту ситуацию иллюстрирует следующий пример спецификации лексем для лексического анализатора скобочных выражений, где явный вызов стандартных функций input и unput исключает остаток входной строки после обнаружения ошибки расстановки скобок:

%{
extern int bracketcount;
int bracketcount;
%}
%%
        bracketcount = 0;
\(      ECHO; bracketcount++;
\)      { ECHO;
          if(--bracketcount < 0) resync();
        }
\n      return(bracketcount);
.       ;
%%
void resync() {
while(input() != '\n');
unput('\n');
return;
} /* resync */

Правила этого примера обеспечивают проверку соответствия открывающих и закрывающих круглых скобок в каждой входной строке, содержащей произвольное алгебраическое или арифметическое выражение, игнорируя при этом любые символы потока стандартного ввода кроме круглых скобок и перевода строки. Они реализуют упрощенный вариант алгоритма Рутисхаузера, где лексический анализ скобочных выражений осуществляется по значению счетчика скобок. В этом алгоритме каждая очередная открывающая скобка увеличивает, а каждая закрывающая скобка уменьшает значение счетчика на 1. Любые другие символы выражения не изменяют значения счетчика скобок. Анализ скобок завершается, если значение счетчика скобок отрицательно или когда достигнут конец выражения. При правильной расстановке скобок значение счетчика в конце выражения должно быть равно нулю, а внутри выражения всегда неотрицательно.
В правилах, реализующих этот алгоритм, для хранения значения счетчика скобок используется внешняя целочисленная переменная bracketcount, которая объявляется в блоке описаний и инициализируется нулем перед обработкой каждой входной строки в действии начального правила без регулярного выражения. Инкремент значения переменной bracketcount обеспечивает действие первого полного правила, когда во входном потоке обнаружена открывающая круглая скобка. Второе правило гарантирует декремент значения переменной bracketcount при обнаружении во входном потоке закрывающей круглой скобки, а также принудительное исключение всех символов до конца строки, если ее величина становится отрицательной.
Исключение остатка строки реализует прикладная функция resync, которая специфицирована в секции подпрограмм для явного вызова стандартных функций input и unput. При этом циклический вызов функции input исключает из входного потока все символы до конца строки. Функция unput возвращает обратно во входной поток символ перевода строки для обработки по третьему полному правилу. Это правило обеспечивает естественное или аварийное завершение лексического анализа, в конце входной строки, содержащей скобочное выражение. Последнее правило с пустым действием необходимо для того, чтобы блокировать обработку любых входных символов, кроме специфицированных в регулярных выражениях предшествующих правил.
Результат проверки правильности расстановки скобок с помощью рассмотренной системы правил можно оценить по последнему значению переменной bracketcount, которое возвращается оператором return системы программирования C в действии третьего полного правила. Скобочное выражение считается корректным, если значение переменной bracketcount после завершения лексического анализа равно нулю. Если значение переменной bracketcount положительно, то в регулярном выражении открывающих скобок больше, чем закрывающих на величину значения счетчика скобок. Если значение переменной bracketcount отрицательно, то закрывающих скобок больше, чем предшествующих им открывающих скобок.
Кроме прикладных и перегруженных стандартных функций в секции подпрограмм может быть специфицирован исходный код основной функции main, которая необходима, когда лексический анализатор реализуется в формате выполняемого модуля. Основная функция main в данном случае должна содержать вызов функции yylex, которая формируется генератором LEX по действиям правил и обеспечивает выполнение процедуры лексического анализа.
В стандартном варианте, который содержится в библиотеке объектных модулей генератора LEX, компонуемой при сборке выполняемого модуля лексического анализатора, исходный код основной функции main имеет следующий вид:

main() {
yylex();
exit(0);
} /* main */

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

int main() {
while(feof(stdin) == 0)
  switch(yylex()) {
    case 0:    puts(" OK (~)");  /* Правильная расстановка скобок */
               break;
    case -1:   puts((" ER (<)"); /* Пропущена открывающая скобка */
               break;
    default:   puts((" ER (>)"); /* Не хватает закрывающих скобок */
               break;
   } /* switch */
exit(0);
} /* main */

В данном случае основная функция main обеспечивает визуальную диагностику результатов лексического анализа скобочных выражений. Ее стандартный исходный код может быть перегружен в секции подпрограмм файла спецификации лексем или задан в отдельном файле, который после компиляции компонуется при сборке лексического анализатора.
Для выполнения процедуры лексического анализа входных строк в основной функции main предусмотрен циклический вызов функции yylex, которая формируется генератором LEX по действиям правил. Ее различные коды возврата, передаваемые оператором return в действиях правил, соответствуют трем альтернативам оператора switch в основной функции main. Каждая альтернатива реализует стандартный вывод диагностического сообщения, которое отражает результат лексического анализа расстановки скобок в текущей строке стандартного ввода. Для отображения диагностических сообщений во всех альтернативах используется библиотечная функция puts системы программирования C. Цикл вызова функции yylex и диагностика ее кода возврата продолжается, пока не достигнут конец потока стандартного ввода, который идентифицирует библиотечная функция feof системы программирования C.