Особо надо оговорить обработку команды nroff ".ex". При получении лексемы, соответствующей этой команде, синтаксический анализатор завершает свою работу, возвращая в головную функцию значение "0", свидетельствующее о нормальном завершении работы.
LEX.
Lex(CP) - это генератор программ, предназначенных для лексической обработки входного потока символов. На вход подается высокоуровневая проблемно-ориентированная спецификация для выделения символьных строк, на выходе получается программа на языке Си для распознавания регулярных выражений. Регулярные выражения задаются пользователем во входной спецификации для построителя. Построенная с помощью lex программа ищет во входном потоке регулярные выражения и разбивает его на строки, удовлетворяющие спецификации. По завершении распознавания цепочки символов выполняются задаваемые пользователем фрагменты программы. В исходном файле для lex устанавливается связь между регулярными выражениями и фрагментами программного кода. При появлении на входе сгенерированной программы некоторого регулярного выражения выполняется соответствующий фрагмент.
Для полного оформления задачи пользователь задает дополнительные части программы, включая подготовленные другими генераторами. Программа-распознаватель генерируется в виде фрагментов программ на языке Си. lex не является законченным языком, это всего лишь генератор, дополняющий язык Си новыми возможностями.
Построитель преобразует спецификации и действия, задаваемые пользователем (в этой главе мы будем называть это исходным текстом), в программу на Си. Эта программа распознает во входном потоке регулярные выражения и при каждом обнаружении выполняет соответствующие действия.
Регулярные выражения.
Регулярное выражение определяет множество цепочек, удовлетворяющих некоторому условию. В него входят текстовые символы (совпадающие с символами в сравниваемых строках) и управляющие (определяющие повторение, выбор и прочие режимы).
К управляющим относятся следующие символы:
"\[]^?.*+|()$/{}%<>
Если какие-либо из перечисленных символов должны использоваться как обычные, их нужно экранировать либо индивидуально с помощью обратной дробной черты (\), либо в виде последовательности, заключая в кавычки. Кавычки указывают, что содержащаяся в них строка должна рассматриваться как текстовые символы. Экранироваться может и часть строки. Экранирование текстовых символов необязательно, хотя никакого вреда не приносит.
Механизм экранирования полезен и при необходимости вставки в регулярное выражение символа пробела. Обычно пробелы или табуляции завершают правила. Любые пробелы, не содержащиеся в скобках, должны
экранироваться. Распознаются также несколько специальных последовательностей языка Си:
\n перевод строки
\t табуляция
\b шаг назад
\ обратная дробная черта
Так как в выражении перевод строки недопустим, он должен экранироваться. Заметьте, что любой символ всегда является текстовым. Исключение составляют пробелы, табуляции, переводы строки и приведенные выше управляющие символы.
Запуск программы.
При компиляции исходной программы можно выделить два шага. Во-первых, исходный файл должен быть преобразован в сгенерированную программу на языке высокого уровня. Затем эта программа компилируется и компонуется, обычно вместе с библиотекой подпрограмм lex. Сгенерированная программа помещается в файл lex.yy.c. В программе может использоваться стандартная библиотека ввода-вывода языка Си.
Результирующая программа помещается в файл a.out и может впоследствии быть выполнена.
Классы символов.
Классы символов задаются с помощью квадратных скобок. Конструкция:
[abc]
удовлетворяет одному символу из множества a, b, c. Внутри скобок большая часть операторов игнорируется. Специальными являются только три: обратная дробная черта (\), минус (-) и стрелка вверх (^). Минус определяет диапазон символов, например:
[a-z0-9<>_]
определяет класс символов, состоящий из строчных букв, цифр, угловых скобок и знака подчеркивания. Диапазоны могут задаваться в любом порядке.
Использование минуса между двумя символами, не являющимися только прописными, только строчными или только цифрами, зависит от реализации и приводит к выдаче предупреждающего сообщения. Если минус должен входить в определяемый класс, его нужно поместить или первым или последним. Таким образом
[-+0-9]
удовлетворяют всем цифрам и знакам плюс и минус.
При определении классов оператор ^ должен быть первым символом после открывающей скобки, он указывает, что полученная строка должна рассматриваться как исключение из всего множества символов ЭВМ. Таким образом
[^abc]
удовлетворяет всем символам, кроме a,b и с, включая все специальные и управляющие символы, а
[^a-zA-Z]
удовлетворяет любому символу, не являющемуся буквой. Обратная дробная черта играет роль экранирующей последовательности для любого символа в квадратных скобках, который, если перед ним стоит обратная дробная черта, рассматривается буквально.
Задание произвольных символов.
Для указания любого символа используется точка (.), удовлетворяющая всем символам, кроме перевода строки. Можно указывать восьмеричные коды, хотя данный способ немобилен. Например,
[40-176]
удовлетворяет всем печатаемым символам кода ASCII, от восьмеричного 40 (пробел) до 176 (черта сверху).
Задание вариантов.
Знак вопроса означает вариант в регулярном выражении. Например
ab?c
удовлетворяет либо ab или abc.
Указание повторений.
Повторяющиеся классы указываются операторами * и +. Например,
a*
удовлетворяет любому количеству (включая 0) последовательно идущих символов a, в то время, как
a+
удовлетворяет одному или нескольким символам. Например,
[a-z]+
удовлетворяет всем строкам из строчных букв, а
[A-Za-z][A-Za-z0-9]*
удовлетворяет всем алфавитно-цифровым строкам, начинающимся с буквы.
Альтернативы и группирование.
Вертикальная черта указывает альтернативы. Например,
(ab|cd)
удовлетворяет либо ab либо cd. Для группирования применяются скобки, хотя на верхнем уровне они не обязательны. Например
ab| cd
аналогично предыдущему примеру. Скобки используется для более сложных выражений, например
(ab|cd+)?(ef)*
удовлетворяет таким строкам, как abefef, efefef, cdef и cddd, но не abc, abcd или abcdef.
Чувствительность к контексту.
Lex распознает ограниченный объем окружающего контекста. Два простейших оператора - ^ и $. Если первым символом выражения указан ^, оно будет удовлетворяться при расположении в начале строки (после символа перевода строки или в начале входного потока). Такой смысл не противоречит значению этого символа при задании классов, так как в этом случае он указывается внутри квадратных скобок. Если последним символом выражения служит $, выражение будет удовлетворяться при нахождении в конце строки. Последний оператор - частный случай более общего оператора /, задающего правый контекст. Выражение
ab/cd
удовлетворяет строке ab только в том случае, если за ней следует cd. Таким образом
ab$
аналогично
ab/\n
Задание повторяющихся выражений.
Фигурные скобки задают либо повторение (если внутри цифры), либо определение подстановки (если внутри имя). Например
{digit}
ищет заранее определенную строку с именем digit и вставляет ее в заданной точке. А выражение
a{1,5}
ищет от одного до пяти вхождений символа a.
Задание определений.
Определения помещаются в первой части спецификации перед правилами и завершаются символом процента.
Задание действий.
Если выражение удовлетворяет некоторому фрагменту вводимого текста, lex выполняет соответствующее действие. В этом разделе описываются некоторые особенности, помогающие при написании действий. Существует действие по умолчанию, заключающееся в копировании входного потока в выходной. Копированию подвергаются строки, не удовлетворившие правилам. Таким образом, для поглощения всего входного потока нужно задать выражение, удовлетворяющее всем символам. При использовании lex совместно с yacc это считается нормальной ситуацией. Вы можете рассматривать копирование как некоторое действие, которое можно не указывать.
Одно из простейших действий - игнорирование входного потока. Это выполняется с помощью пустого оператора Си (;).
Еще один способ избежать задания действий - символ повторения (|), указывающий, что действие этого правила аналогично действию для последующего.
Иногда правила некорректно распознают символы на границах входного потока. Для этой ситуации удобны две функции. Yymore() указывает, что следующее входное выражение должно помещаться в конец только что найденного. Обычно следующее выражение затирает текущее содержимое yytext. Yyless(n) вызывается тогда, когда в данный момент нужны не все символы, удовлетворившие текущему правилу. Аргумент указывает количество символов, возвращаемых во входной поток. Это обеспечивает просмотр вперед, но в несколько иной форме, нежели при $.
Можно пользоваться и внутренними функциями ввода-вывода. К ним относятся:
1. input() следующий символ из потока;
2. output(c) вывод символа в поток;
3. unput(c) возврат символа в поток.
По умолчанию эти функции определены как макросы, но пользователь может вместо них использовать собственные функции. Эти функции определяют взаимосвязь между внешними файлами и внутренним представлением символов, поэтому их модификация должна быть согласованной и непротиворечивой. Они могут переопределяться для ввода и вывода во внутреннюю память или в другие процессы, но набор символов должен быть единым, нулевой значение, возвращаемое input(), должно означать конец файла, взаимосвязь между input и unput должна быть сохранена, иначе не будет работать просмотр вперед.