емы "-" в момент, когда разобранная часть
строки приведена к виду expr*expr. Два возможных действия
анализатора состоят в следующем:
Можно ввести следующий символ и без применения правила
подстановки перейти в новое состояние. В разделе 1 мы
назвали такое действие сдвигом. Выбор сдвига приведет к
тому, что в одном из следующих состояний ко второй
части конструкции для приведения ее к expr будет приме-
нено правило (3), а затем вся полученная конструкция
сведется к expr применением правила (4).
- 20 -
Можно сразу применить к конструкции expr*expr правило
(4), тем самым приведя ее к expr, и без ввода нового
символа перейти в очередное состояние. Такое действие в
разделе 1 было названо сверткой. Использование свертки
в данном состоянии приведет к применению в дальнейшем
правила (3) для свертывания оставшейся части конструк-
ции в expr.
Неоднозначность такого рода будем называть конфликтом
"сдвиг/свертка". (Выше этот конфликт был умышленно показан
на примере выражения, порядок разбора которого существен в
связи с различием приоритетов операций. На самом деле конф-
ликт сдвиг/свертка возникает и при разборе такой строки, как
expr-expr-expr. В этом случае разница в разборе ведет лишь
к трактовке операции "-" как лево- или правоассоциативной.
Однако, как известно, ассоциативность иногда играет важную
роль, например, для операций присваивания, возведения в сте-
пень. Возможен другой вид конфликта, состоящий в выборе
между двумя возможными свертками; будем называть его конф-
ликтом "свертка/свертка". Для примера подобного конфликта
приведем грамматику, задающую десятичную и шестнадцатеричную
форму записи константы:
const: const_10 /*1*/
| const_16 ; /*2*/
const_10: dec_sequence; /*3*/
const_16: hex_sequence 'x'; /*4*/
dec_sequence:digit /*5*/
| dec_sequence digit; /*6*/
hex_sequence:digit /*7*/
| ABCDEF /*8*/
|hex_sequence digit /*9*/
|hex_sequence ABCDEF; /*10*/
ABCDEF : 'A'|'B'|'C'|'D'|'E'|'F';
digit:'0'|'1'|'2'|'3'|'4'|'5'|'6'
|'7'|'8'|'9';
При появлении на входе первой же десятичной цифры (если
с нее начинается последовательность) после ее замены нетер-
миналом digit возникает конфликт между двумя возможными
свертками: к нетерминалу dec_sequence в результате примене-
ния правила (5) и к нетерминалу hex_sequence с помощью пра-
вила (7). Заметим, что эта грамматика, в отличиe от грамма-
тики в предыдущем примере, не позволяет корректно разобрать
какую-либо строку двумя способами и в принципе нетерминал
const определен однозначно. Однако, алгоритм разбора с
просмотром вперед на 1 символ не в состоянии правильно осу-
ществить выбор нужного правила. Следовательно, в этом случае
речь идет о неоднозначности грамматики по отношению к приня-
тому в yacc методу разбора. Поскольку вопрос о принципиаль-
ной неоднозначности грамматики формально неразрешим, будем в
дальнейшем понимать под неоднозначностью невозможность для
- 21 -
анализатора в некоторые моменты разбора выбрать очередное
действие. Каждая ситуация (т.е. появление в некотором состо-
янии некоторой входной лексемы), которая при разборе спо-
собна вызвать конфликт "сдвиг/свертка" или "свертка/
свертка", выявляется yacc уже на этапе построения граммати-
ческого анализатора. При этом yacc выдает сообщение о числе
выявленных конфликтных ситуаций обоих видов, а в выходной
файл y.output (если он формируется) помещается подробное
описание всех конфликтов. Однако, yacc не считает наличие
конфликтов фатальной ошибкой грамматики и строит граммати-
ческий анализатор, заранее разрешая все возможные конфликты
путем выбора в каждой ситуации единственного действия.
При работе yacc используются два способа разрешения
конфликтов. Первый способ действует по умолчанию (т.е. при
отсутствии специальной пользовательской информации) и заклю-
чается в применении следующих двух правил для выбора дейст-
вия в конфликтных ситуациях:
В случае конфликта сдвиг/свертка по умолчанию делается
сдвиг.
В случае конфликта свертка/свертка по умолчанию дела-
ется свертка по тому из конкурирующих правил, которое
задано первым в спецификационном файле.
Грамматический анализатор, построенный с использованием
этих правил, может не обеспечивать "правильного" с точки
зрения пользовательской грамматики разбора. В частности,
для первого из приведенных выше примеров разбор заключался
бы в сворачивании арифметических выражений справа налево без
учета приоритетов операций. Во втором примере в результате
замены первой конструкции digit нетерминалом dec_sequence
все числа, начинающиеся с цифры, разбирались бы как десятич-
ные, а появление одной из букв от A до F или символа "x" в
конце числа неверно трактовалось как ошибка во входном
тексте.
Однако, в ряде ситуаций описанный способ разрешения
конфликтов приводит к нужному результату. Например, рассмот-
рим фрагмент грамматики языка программирования, описывающий
условный оператор:
оператор: if '(' условие ')' оператор /*1*/
|
if '(' условие ')' оператор else
оператор; /*2*/
Входная строка вида:
if(C1) if(C2) S1 else S2
вызвала бы при разборе конфликт сдвиг/свертка в момент
- 22 -
просмотра символа else. Введенная часть строки к этому вре-
мени имеет вид:
if (условие) if (условие) оператор
Если выполнить свертку второй части конструкции по правилу
(1), то строка сведется к:
if (условие) оператор
(Заметим, что применить еще раз правило(1) мешает просмот-
ренный заранее символ else). После ввода конструкции S2 и
замены ее нетерминалом оператор к строке:
if (условие) оператор else оператор
будет применено правило (2). Полученный разбор соответствует
следующей интерпретации входной строки:
if (C1) {if(C2) S1} else S2
При альтернативном подходе в случае применения сдвига в
момент появления else входная строка была бы введена пол-
ностью:
if (условие) if (условие) оператор
else оператор
Ко второй части строки можно применить правило (2), а затем
свернуть полученную конструкцию:
if (условие) оператор
по правилу (1). Такой разбор соответствует второй возможной
интерпретации входной строки:
if (C1) {if(C2) S1 else S2}
Как известно, в большинстве языков программирования принята
именно эта интерпретация (каждый else относится к ближайшему
предшествующему if). Значит, выбор сдвига, осуществляемый по
умолчанию, для данной грамматики верен.
В качестве рекомендации отметим,что применение принципа
умолчания для конфликтов сдвиг/свертка приводит к положи-
тельному результату, если в грамматике принята правоассоциа-
тивная интерпретация соответствующих конструкций и для них
отсутствует понятие приоритета. Что касается конфликтов
свертка/свертка, то стандартный способ их разрешения оказы-
вается полезным только тогда, когда при любых конфликтах
между данными двумя правилами справедлив выбор одного и того
же правила.
- 23 -
В любом случае, если yacc сообщил о наличии конфликтных
ситуаций, пользователь должен тщательно проанализировать
содержательный смысл каждого конфликта и правильность выб-
ранного yacc действия. Вся необходимая для этого информация
содержится в файле y.output, структура которого будет расс-
мотрена ниже. Если оказалось, что конфликты разрешены неу-
довлетворительно, то грамматика должна быть перестроена или
уточнена пользователем. В случае конфликтов свертка/свертка
всегда требуется изменение самих грамматических правил; для
конфликтов сдвиг/свертка есть возможность без перестройки
правил уточнить грамматику путем задания информации о прио-
ритетах и ассоциативности лексем и правил.
В качестве примеров устранения конфликтов путем измене-
ния правил приведем перестроенные варианты рассматривавшихся
выше грамматик. Поскольку исходные конфликтные грамматики
полностью удовлетворяют требованиям генерируемых ими языков,
но содержат недостаточно информации для однозначного раз-
бора, перестройка правил носит уточняющий характер.
Перестроенная грамматика константного арифметического
выражения:
expr: expr1
|
expr '+' expr1
|
expr '-' expr1;
expr1: CONST
|
expr1 '*' CONST
|
expr1 '/' CONST;
Ниже будет приведен также вариант грамматики, полученной из
исходной введением приоритетов (без перестройки правил).
Перестроенная грамматика для задания константы:
const: const_10
| const_16;
const_10: dec_sequence ;
const_16: hex_sequence 'x'
| dec_sequence 'x';
dec_sequence: digit
| dec_sequence digit;
hex_sequence: ABCDEF
| dec_sequence ABCDEF
| hex_sequence ABCDEF
|hex_sequence dec_sequence;
ABCDEF:...
digit:...
- 24 -
Рассмотрим теперь второй из используемых yacc способов раз-
решения конфликтов, базирующийся на задании пользователем
информации о приоритетах и ассоциативности. Отметим предва-
рительно две основные причины конфликтности грамматик:
принципиальная невозможность задать генерируемый язык
бесконфликтной грамматикой;
недостаточность информации для построения однозначного
грамматического разбора.
Грамматики, конфликтные по второй причине, всегда
допускают преобразование правил до полного устранения конф-
ликтов; в случае конфликтов сдвиг/свертка yacc всегда может
построить для этих грамматик корректный разбор путем разре-
шения конфликтов. Для грамматик, конфликтных по причине 1,
попытки разрешения конфликтов не приведут к нужному резуль-
тату. Однако, надо убедиться в том, что конфликтная грамма-
тика действительно отражает входной язык: возможно, конф-
ликты не имеют отношения к этому языку, а вызваны неодноз-
начностью другого, реально описанного языка. Вообще, конф-
ликты стоит рассматривать как повод проверить грамматику на
соответствие языку (хотя последнее не гарантируется и
отсутствием конфликтов). Задание приоритетов для неверной
грамматики не приведет к генерации нужного языка, но может
замаскировать ошибки, сняв вопрос о конфликтах.
Приоритетное разрешение конфликтов сдвиг/свертка сос-
тоит в том, что с обоими действиями yacc ассоциирует приори-
теты (со сдвигом - приоритет лексемы, чтение которой вызы-
вает данный конфликт, со сверткой - приоритет конкурирующего
правила) и выбирает более приоритетное действие. В случае
равенства приоритетов yacc руководствуется при выборе
свойством ассоциативности. Приоритеты и ассоциативность
отдельных лексем (явно) и правил (явно и неявно) задаются
пользователем, все остальные приоритеты считаются неизвест-
ными. yacc использует для разрешения конфликта данный спо-
соб, если известны приоритеты обоих конкурирующих действий.
Поэтому для разрешения ряда конфликтов на приоритетной
основе необходимо установить приоритеты участвующих в них
лексем и правил.
Следует понимать,что задание приоритетов не ведет к
устранению конфликтов и не делает грамматику однозначной. Но
в отличие от конфликтов, разрешаемых yacc по принципу умол-
чания, пользователь получает здесь возможность управлять
разрешением конфликтов. yacc, сообщая общее число конфлик-
тов, не учитывает в нем конфликтов, разрешенных в соответст-
вии с информацией о приоритетах и не включает в выходной
файл y.output описания этих конфликтов.
Приоритеты и ассоциативность лексем задаются в секции
деклараций с помощью директив трех видов:
- 25 -
%left <список_лексем>
%right <список_лексем>
%nonassoc <список_лексем>
Каждая директива приписывает всем перечисленным в списке
лексемам одинаковый приоритет. Директивы %left и %right
одновременно задают соответственно левую или правую ассоциа-
тивность лексем. Директива %nonassoc говорит об отсутствии
у перечисленных лексем свойства ассоциативности. Устанавли-
ваемый директивами приоритет не имеет численного выражения,
а его относительное значение возрастает сверху вниз.
Пример задания приоритетов лексем:
%token OR NOR XOR AND NAND
%right '='
%left OR NOR XOR
%left AND NAND
%left '+' '-'
%left '*' '/'
/* самый низкий приоритет имеет лексема "=",
самый высокий - лексемы "*" и "/"
*/
Приоритет правила автоматически определяется приорите-
том последней лексемы в теле правила. Если в секции деклара-
ций для этой лексемы не задан приоритет или если правая
часть правила вообще не содержит лексем, то приоритет пра-
вила не определен. Этот принцип можно отменить явным зада-
нием приоритета правила равным приоритету любой (имеющей
приоритет) лексемы с помощью директивы:
%prec <лексема>
помещенной вслед за правой частью правила (перед точкой с
запятой или действием). Например, правилу:
expr: '-' expr %prec '*';
директива %prec придает приоритет лексемы "*" (лексема "-"
при задании грамматики выражений часто используется для
обозначения унарной и бинарной операций, имеющих разный при-
оритет; с помощью директивЫ %prec унарной операции можно
приписать более высокий приоритет. Иногда, чтобы связать с
правилом приоритет, не совпадающий с приоритетом ни одной
лексемы, вводят псевдолексему, задав ей в секции деклараций
уникальный приоритет, и приписывают приоритет псевдолексемы
правилу. В примере грамматики настольного калькулятора, при-
водимом в приложении, с операцией "унарный минус" связан
приоритет псевдолексемы UMINUS).
- 26 -
Сформулируем теперь полностью используемые yacc правила
разрешения конфликтов сдвиг/свертка на основе информации о
приоритетах и ассоциативности (напомним, что конфликты
свертка/свертка разрешаются только по принципу умолчания):
Если для входной лексемы и правила заданы приоритеты и
эти приоритеты различны, то выбирается действие с боль-
шим приоритетом. Больший приоритет правила вызывает
свертку по нему, больший приоритет лексемы вызывает
сдвиг.
Если приоритеты заданы и совпадают, то принимается во
внимание заданная одновременно с приоритетом ассоциа-
тивность: в случае левой ассоциативности используется
свертка, в случае правой - сдвиг. Отсутствие свойства
ассоциативности (директива %nonassoc) в данном случае
указывает на ошибку во входном тексте и анализатор
воспримет вызвавшую данный конфликт лексему как ошибоч-
ную.
Если не задан приоритет входной лексемы и/или приоритет
правила, то действует принцип разрешения конфликтов по
умолчанию, в результате чего выбирается сдвиг.
Пример грамматики константного выражения, уточненной зада-
нием приоритетов:
%left '+' '-'
%left '*' '/'
%%
expr: CONST
| expr '+' expr
| expr '-' expr
| expr '*' expr
| expr '/' expr;
9. Структура информационного входного файла y.output
Основную часть данного файла составляет описание состо-
яний построенного грамматического анализатора. Информация о
каждом состоянии приводится в следующем порядке:
- Перечень соответствующих данному состоянию конфигураций
грамматики (конфигурация характеризуется определенным
грамматическим правилом и позицией в его правой части,
достигнутой к данному моменту разбора). Каждая конфигу-
рация представляется правилом с отмеченной с помощью
символа подчеркивания "_" распознанной частью (позицией
конфигурации). Например, конфигурация:
expr: expr +_expr
- 27 -
соответствует распознанной при разборе строки по ука-
занному правилу последовательности символов expr+.
- Действия анализатора при вводе в качестве очередного
просматриваемого символа каждой из лексем. Различные
виды действий указываются следующим образом:
<лексема> сдвиг <номер_состояния> -
сдвиг при вводе данной лексемы в состояние с указанным
номером;
<лексема> свертка <номер_правила> -
свертка при вводе лексемы по правилу с указанным номе-
ром;
<лексема> error -
выдача сообщения об ошибке во входных данных ("синтак-
сическая ошибка") и возврат из процедуры грамматичес-
кого анализа с кодом 1 (дальнейший разбор невозможен);
<лексема> accept -
возврат из процедуры грамматического анализа с кодом 0
(успешное завершение разбора). Последняя из строк,
описывающих действия анализатора, содержит вместо ука-
зания лексемы символ "." и сообщает действие, выполняе-
мое анализатором для всех лексем, не перечисленных в
данном состоянии. Часто эта строка имеет вид:
. error
и указывает, что все перечисленные лексемы в данном
состоянии являются недопустимыми.
- Перечень переходов для данного состояния. Каждый пере-
ход задается строкой
<имя_терминала> переход <номер_состояния>
сообщающей, в какое состояние перейдет анализатор после
свертки указанного нетерминала, если его распознавание
было начато из данного состояния.
Кроме того, описанию состояния может предшествовать
информация о конфликтах обнаруженных yacc для этого состоя-
ния и разрешенных по принципу умолчания. Информация о конф-
ликте содержит тип конфликта (св/св или сдв/св), конкурирую-
щие действия анализатора (при этом для сдвига указывается
номер состояния, для свертки - номер правила) и лексему, при
появлении которой возникает данный конфликт. Узнать, какое
- 28 -
из возможных действий будет выполнено анализатором, можно из
описания самого состояния.
Пример описания состояния:
8:Конфликт сдв/св (сдвиг 5,свертка 2) при +
Состояние 8
a:a_+a
a:a+a_ (2)
+ сдвиг 5
. свертка 2
Состоянию 8 здесь соответствуют две различные позиции, дос-
тигнутые при разборе по правилу
a: a '+' a
Kонфликт между сверткой по этому правилу и сдвигом в состоя-
ние 5 при вводе лексемы "+" разрешен в пользу сдвига. Ввод
остальных лексем вызывает свертку.
После описания состояния возможен ряд сообщений о нес-
вернутых правилах (с указанием этих правил), т.е. о прави-
лах, свертка по которым не будет произведена ни в одном из
состояний. Наличие таких правил с большой вероятностью сви-
детельствует о некорректности грамматики.
В конце файла приводится информация статистического
характера о реальном и предельном количестве терминальных и
нетерминальных символов, грамматических правил и состояний.
Фиксируется число конфликтов каждого типа, число различных
действий грамматического анализатора, занимаемый им объем
памяти, приводятся данные об использовании внутренних струк-
тур данных (множеств).
10. Обработка ошибок при грамматическом разборе
Если входной поток не удовлетворяет заданной грамма-
тике, то грамматический анализатор в момент ввода лексемы,
делающей невозможным продолжение разбора, фиксирует ошибку.
Эту лексему мы в дальшейшем будем называть ошибочной лексе-
мой. Реально ошибка может быть вызвана не только неверными
входными данными, но и некорректностью самого грамматичес-
кого анализатора, являющейся следствием некорректной грамма-
тики.
Стандартной реакцией грамматического анализатора на
ошибку является выдача сообщения ("синтаксическая ошибка") и
прекращение разбора. Эту реакцию можно несколько изменить,
например, сделать сообщение об ошибке несколько более инфор-
мативным, задав собственную процедуру yyerror. Однако, наи-
более важная задача состоит в том, чтобы заставить анализа-
тор в этом случае продолжать просмотр входного потока, в
- 29 -
частности, для выявления остальных ошибок. Применяемый yacc
механизм восстановления основан на чтении и отбрасывании
некоторого числа входных лексем; от пользователя требуется
введение дополнительных грамматичсеких правил, указывающих,
в каких конструкциях синтаксические ошибки являются допусти-
мыми (в отношении возможности восстановления). Одновременно
эти правила определяют путь дальнейшего разбора для ошибоч-
ных ситуаций. Для указания точек допустимых ошибок исполь-
зуется зарезервированное с этой целью имя лексемы error.
Пример:
a: b c d ; /*1*/
a: b c error; /*2*/
d: d1 d2 d3; /*3*/
Второе правило указывает путь разбора в случае, если при
распознавании нетерминала a встретится ошибка после выделе-
ния элементов b и c.
yacc обрабатывает правила, содержащие лексему error,
так же, как все остальные правила. В результате в ряде сос-
тояний построенного анализатора оказывается предусмотренным
действие для лексемы error (отличное от действия error).
Будем говорить, что в этих состояниях лексема error допус-
тима. Рассмотрим порядок работы анализатора при появлении
во входном потоке ошибочной лексемы (т.е. лексемы, ввод
которой в данном состоянии вызывает действие error):
Фиксируется состояние ошибки; вызывается функция yyer-
ror для выдачи сообщения.
Путем обратного просмотра пройденных состояний,начиная
с данного, делается попытка найти состояние, в котором
допустима лексема error. Отсутствие такого состояния
говорит о невозможности восстановления, и разбор прек-
ращается.
Осуществляется возврат в найденное состояние (кроме
случая, когда им является непосредственно то состояние,
в котором встретилась ошибка)
Выполняется действие, заданное в этом состоянии для
лексемы error. Очередной входной лексемой становится
лексема, вызвавшая ошибку.
Разбор продолжается, но анализатор остается в состоянии
ошибки до тех пор, пока не будут успешно прочитаны и
обработаны три подряд идущие лексемы. При нахождении
анализатора в состоянии ошибки отличие в обработке оши-
бочной лексемы заключается в том, что сообщения об
ошибке не выдается, а сама лексема игнорируется.
- 30 -
После обработки трех допустимых лексем считается, что
восстановление произошло, и анализатор выходит из сос-
тояния ошибки.
Итак, грамматический анализатор, встретив ошибку, пыта-
ется найти ближайшую точку во входном потоке, где разрешена
лексема error. При этом сначала делается попытка возврата в
рамках правила, по которому шел разбор в момент появления
ошибочноЙ лексемы, затем поиск распространяется на правила
все более высокого уровня. В примере, приведенном в начале
раздела, ввод недопустимой лексемы после того, как прочитана
строка b c d1 d2 вызовет возврат к состоянию, характеризую-
щемуся конфигурациями:
a: b c_d;
a: b c_error;
и продолжение разбора по правилу (2).
Часто правила, учитывающие возможность ошибки, задаются
на уровне основных структурных единиц входного текста. Нап-
ример, для пропуска в тексте ошибочных операторов может быть
использовано правило
оператор: error;
При этом восстановление из состояния ошибки произойдет после
нахождения трех лексем, которые могут следовать после опера-
тора, например, начинать новый оператор. Если точно распоз-
нать начало оператора невозможно, то ошибочное состояние
может быть подавлено преждевременно, а обработка нового опе-
ратора начата с середины ошибочного, что, вероятно, приведет
к повторному сообщению об ошибке (на самом деле не существу-
ющей). Учитывая это, более надежного результата следует ожи-
дать от правил вида:
оператор: error ';'
Здесь восстановление произойдет только после нахождения ";"
и двух начальных лексем следующего оператора; все лексемы
после найденной ошибочной до ";" будут отброшены.
С правилами, включающими лексему error, могут быть свя-
заны действия. С их помощью пользователь может самостоя-
тельно обработать ошибочную ситуацию. Кроме обычных операто-
ров, здесь можно использовать специальные операторы yyerror
и yyclearin, которые yacc на макроуровне расширяет в нужные
последовательности. Оператор yyerror аннулирует состояние
ошибки. Таким образом, можно отменить действие принципа
"трех лексем". Это помогает предотвратить маскирование новых
ошибок в случаях, когда конец ошибочной конструкции распоз-
нается самим пользователем или однозначно определяется в
правиле по меньшему числу лексем.
- 31 -
Оператор yyclearin стирает хранимую анализатором пос-
леднюю входную лексему, если поиск нужной точки для возоб-
новления ввода обеспечивается в заданном пользователем
действии.
Приведем общую форму правила с восстановительным дейст-
вием
оператор : error {resynch();
yyclearin;
yyerror;}
Предполагается, что пользовательская процедура resynch()
просматривает входной поток до начала очередного оператора.
Вызвавшая ошибку лексема, хранимая анализатором в качестве
входной лексемы, стирается, после этого гасится состояние
ошибки.
При построении анализаторов, работающих в интерактивном
режиме, для обработки ошибок рекомендуются правила вида:
входная_строка : error '\n' {yyerrok;
printf("Повторите последнюю строку \n");}
входная_строка {$$=$4;}
В действии, предусмотренном после ввода ошибочной строки,
пользователю делается подсказка, а состояние ошибки гасится.
Значением нетерминала после свертки здесь становится значе-
ние повторно введенной строки.
11. Диагностика ошибок
yacc выдает ряд сообщений об ошибках в случае невозмож-
ности построения грамматического анализатора. В тексте сооб-
щения, предваряемом заголовком "ошибка:", содержится указа-
ние причины ошибки и номер просматриваемой в момент ее появ-
ления строки спецификационного файла. Перечислим основные
типы фиксируемых yacc ошибок:
неверно заданные флаги командной строки;
невозможность открытия входного или временных файлов;
отсутствие файла /usr/lib/yaccpar с текстом процедуры
yyparse;
неверная структура спецификационного файла;
ошибки в задании директив;
синтаксические ошибки в описании грамматических правил,
незавершенность комментариев, строк символов и дейст-
вий;
- 32 -
некорректное использование псевдопеременных;
неопределенные нетерминальные символы;
нарушение количественных ограничений (по числу терми-
нальных или нетерминальных символов, грамматических
правил, состояний и проч.).
- 33 -
Приложение 1
Ниже приведен полный входной файл для yacc, реализующий
небольшой настольный калькулятор; калькулятор имеет 26
регистров, помеченных буквами от a до z и разрешает исполь-
зовать арифметические выражения, содержащие операции +, -,
*, /, % (остаток от деления), & (побитовое и), | (побитовое
или) и присваивание. Как и в Си, целые числа, начинающиеся с
0, считаются восьмеричными, все остальные - десятичными.
В примере демонстрируются способы использования приори-
тетов для разрешения конфликтов, а также простые операции по
восстановлению из состояния ошибки. Калькулятор работает в
интерактивном режиме с построчным формированием выхода.
%token DIGIT LETTER /* имена лексем */
%left '|' /* задание приоритетов */
%left '&' /* операций */
%left '+' '-'
%left '*' '/' '%'
%left UMINUS /* установка приоритета
операции унарный минус */
%{ /* oписания, используемые */
int base, regs[26]; /* в действиях */
%}
%% /* начало секции правил */
list:
| list stat '\n'
| list stat error '\n' {yyerrok; }
stat:
expr {printf("%d\n",$1);}
| LETTER '=' expr {regs[$1]=$3; }
expr:
'(' expr ')' { $$=$2; }
| expr '+' expr { $$=$1+$3; }
| expr '-' expr { $$=$1-$3; }
| expr '*' expr { $$=$1*$3; }
| expr '/' expr { $$=$1/$3; }
| expr '%' expr { $$=$1%$3; }
| expr '&' expr { $$=$1&$3; }
| expr '|' expr { $$=$1|$3; }
| '-' expr %prec UMINUS { $$= -$2; }
| LETTER { $$=regs[$1]; }
| number;
number:
DIGIT { $$=$1; base=10;
if($1==0) base=8; }
- 34 -
| number DIGIT {$$=base*$1+$2; }
%% /* начало секции программ */
/*
* Программа лексического анализа
* для строчных латинских букв
* возвращает LETTER,
* значение yylval от 0 до 25;
* для цифр - DIGIT,
* значение yylval от 0 до 9;
* остальные символы возвращаются
* непосредственно
*/
yylex()
{
int c;
while( (c=getchar()) == ' ' );
if( c>='a' && c<='z' ) {
yylval = c - 'a';
return(LETTER);
}
if( c>='0' && c<='9' ) {
yylval = c - '0';
return(DIGIT);
}
return(c);
}
- 35 -
Приложение 2
Анализ и преобразование в матричную форму входных дан-
ных для задачи линейного программирования.
Входная информация из обычной алгебраической формы:
c1X1+c2X2+...+cnXn -> min(max)
a11x1+a12X2+...+a1nXn=b1
am1X1+am2X2+...+amnXn=bm
преобразуется в матричную:
C: c1 c2 ... cn
A: a11 a12 ... a1n
...
am1 am2 ... amn
B: b1 b2 ... bm
Одновременно изменяются знаки коэффициентов при неизвестных
в ограничениях с отрицательным свободным членом, а также
знаки коэффициентов линейного функционала в случае его мак-
симизации. С иллюстративной целью допускаются некоторые
варианты синтаксической записи. Предусмотрена возможность
повторного задания ошибочных строк.
%token число Xi оптим
%%
злп:функционал '\n' система_ограничений
{final();}
| система_ограничений функционал '\n'
{ final();}
функционал: линейная_функция {stf();}
/* По умолчанию - минимизация */
| оптим '(' линейная_функция ')'
{if($1) conv(); stf();}
/* В случае максимизации выполнить conv() */
| линейная_функция '-''>' оптим
{if($4) conv();stf();}
линейная_функция: элем1
| линейная_функция элем ;
элем: знак число Xi {stc($1*$2,$3); }
| знак Xi '*' число { stc($1*$4,$2);}
| знак Xi { stc($1,$2);}
/* Формы записи коэффициентов */
элем1: элем
| число Xi { stc($1,$2);}
| Xi '*' число { stc($3,$1);}
- 36 -
| Xi { stc(1,$1); }
знак: '+' {$$=1; }
| '-' {$$= -1;}
система_ограничений: ограничение '\n'
| система_ограничений ограничение '\n'
| система_ограничений error '\n'
{aclear();yyerrok;
printf("повторите последнюю строку\n");}
/* В случае ошибки: стирание инф. о строке,
восстановление и выдача подсказки */
ограничение: линейная_функция '=' число
{ stcb($3);}
| линейная_функция '=' знак число
{ if($3<0) conv(); stcb($4);}
/* Если bi<0, выполнить conv() */
%%
#include "stdio.h"
#define MAXM 100 /* предельное число */
#define MAXN 100 /* ограничений и перем.*/
int c[MAXN],b[MAXM],a[MAXM+1][MAXN],
neqs,nx,x[MAXN];
/* Лексический анализатор возвращает:
* для целых чисел - число,
* yylval равно знач. числа;
* для идент.вида xi, i=1,2,...XI*
* yylval равно его порядк. номеру;
* для max/min - оптим,
* yylval равно соответственно 1 или 0
*/
yylex() {
char c,i;
while((c=getc(stdin))==' ');
switch(c) {
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
case'8':
case'9': yylval=c-'0';
while((c=getc(stdin))<='9'&&c>='0')
yylval=yylval*10+c-'0';
ungetc(c,stdin); return(число);
- 37 -
case'm': if((c=getc(stdin))=='i') yylval=0;
else if(c=='a') yylval=1;
else return('m');
while((c=getc(stdin))<='z'&&c>='a');
ungetc(c,stdin); return(оптим);
case'X':
case'x': if((c=getc(stdin))<'0' || c>'9')
return('x');
yylval=0;
while(c<='9'&&c>='0')
{yylval=yylval*10+c-'0';c=getc(stdin);}
ungetc(c,stdin);
for(i=0;i<nx;i++)
if(x[i]==yylval){yylval=i;return(Xi);}
x[nx]=yylval; yylval=nx++;return(Xi);
}
return(c);
}
/* Печать условия задачи в матричной форме */
final() {
char i,j;
printf("c:\n");
for(i=0;i<nx;i++) printf("%8d",c[i]);
printf("\na:\n");
for(i=0;i<neqs;i++) {
for(j=0;j<nx;j++) printf("%8d",a[i][j]);
printf("\n"); }
printf("b:\n");
for(i=0;i<neqs;i++) printf("%8d",b[i]);
}
/* Изменение знаков коэффициентов */
conv() {
char i;
for(i=0;i<nx;i++) a[neqs][i]*=(-1);
}
/* Запоминание коэффициентов функционала */
stf() {
char i;
for(i=0;i<nx;i++)
{ c[i]=a[neqs][i]; a[neqs][i]=0; }
}
/* Запоминание очередного коэффициента */
stc(nmb,adr) {
a[neqs][adr]=nmb; }
/* Запоминание свободного члена */
stcb(nmb) {
b[neqs++]=nmb; }
- 38 -
/* Стирание ошибочной строки*/
aclear(){
char i;
for(i=0;i<nx;i++)
a[neqs][i]=0;
}
- 39 -
Приложение 3
Формальное описание структуры спецификационного файла.
файл_спецификаций:
секция_деклараций '%''%'
'\n' секция_правил '%''%'
'\n' секция_программ
| секция_деклараций '%''%'
'\n' секция_правил ;
секция_деклараций:
| секция_деклараций дир_или_описание
'\n' ;
дир_или_описание:
'%''{''\n'ВНЕШНИЕ_ОПИСАНИЯ
'\n''%''}'
| '%''s''t''a''r''t' ИДЕНТИФИКАТОР
| '%''t''o''k''e''n'список_им_лексем
| ассоциативность список_лексем ;
ассоциативность:
'%''l''e''f''t'
| '%'''r''i''g''h''t'
| '%''n''o''n''a''s''s''o''c' ;
список_им_лексем:
| декл_имени_лексемы
| список_им_лексем
декл_имени_лексемы ;
список_лексем:
декл_лексемы
| список_лексем декл_лексемы ;
декл_имени_лексемы:
ИДЕНТИФИКАТОР
| ИДЕНТИФИКАТОР ЦЕЛОЕ_БЕЗ_ЗНАКА;
декл_лексемы:
декл_лексемы
| декл_литерала;
декл_литерала:
ЛИТЕРАЛ
| ЛИТЕРАЛ ЦЕЛОЕ_БЕЗ_ЗНАКА;
секция_правил:
набор_правил
| '%''{''\n'СИ_ОПЕРАТОРЫ'\n''%''}'
'\n' набор_правил '\n' ;
набор_правил:
правило
| набор_правил ';' правило ;
правило:
левая_часть ':' альтернатива
| правило '|' альтернатива ;
левая_часть: ИДЕНТИФИКАТОР ;
альтернатива:
- 40 -
правая_часть
| правая_часть изм_приор
| правая_часть изм_приор действие
| правая_часть действие ;
правая_часть:
| правая_часть лит_или_идент
| правая_часть действие лит_или_идент;
изм_приор:
секция_программ:
ПРОГРАММНЫЕ_КОМПОНЕНТЫ ;
При описании структуры спецификационного файла не рас-
шифровывались некоторые исходные конструкции, совпадающие с
аналогичными конструкциями Си: идентификатор, литерал, целое
без знака. Не описаны также и более сложные конструкции,
являющиеся фрагментами Си-программ, переносимыми в текст
анализатора (внешние описания, Си-операторы). Под расширен-
ными Си-операторами понимаются операторы с возможным исполь-
зованием псевдопеременных. ПРОГРАММНЫЕ_КОМПОНЕНТЫ включают
операторы препроцессора, описания внешних переменных и функ-
ций (возможный состав их приводится в разделе 4.3).
- 41 -
СОДЕРЖАНИЕ
. Аннотация ......................................... 2
1. Принципы работы yacc .............................. 3
2. Входные и выходные файлы, структура грамматического
анализатора ....................................... 4
3. Процедура построения грамматического анализатора .. 5
4. Задание входной информации yacc ................... 6
4.1. Структура спецификационного файла ............... 6
4.2. Секция правил ................................... 7
4.3. Секция деклараций ............................... 12
5. Декларация имен лексем ............................ 13
6. Декларация приоритетов и ассоциативности лексем ... 13
7. Декларация имени начального символа ............... 15
7.1. Секция программ ................................. 15
7.2. Действия с использованием псевдопеременных ...... 17
8. Конфликты грамматического разбора ................. 20
9. Структура информационного входного файла
y.output .......................................... 27
10. Обработка ошибок при грамматическом ра