Операционная система UNIX. Руководство программиста

     

Совместное использование lex'а и yacc'а


При работе над компилятором или программой, проверяющей правильность исходного текста, целесообразно воспользоваться мощным инструментальным средством системы UNIX - yacc(1). yacc генерирует функции синтаксического разбора и программы, анализирующие входной текст с целью убедиться в его синтаксической корректности. При разработке компиляторов объединение lex'а и yacc'а часто оказывается плодотворным. Даже если Вы пока не предполагаете использовать lex вместе с yacc, обязательно прочитайте этот раздел, так как он содержит информацию, интересную для всех пользователей lex'а.

Процедура лексического анализа, которую генерирует lex, (но не файл, в котором она сохраняется), имеет имя yylex(). Это удобно, поскольку yacc обращается к лексическому анализатору как раз по этому имени. При использовании lex'а для создания лексического анализатора в компиляторе следует завершать каждое lex- действие оператором return лексема, где лексема - введенное обозначение некоторого целочисленного значения. Возвращаемое значение лексемы указывает процедуре разбора, какую цепочку обнаружил лексический анализатор. Процедура разбора, которую yacc

помещает в файл y.tab.c, вновь получает управление и выполняет очередное обращение к лексическому анализатору, когда ей требуется очередная лексема.

При компиляции различные значения обозначают типы лексем, то есть показывают, является ли лексема определенным зарезервированным словом, идентификатором, константой, арифметической операцией или операцией сравнения. Во всех перечисленных случаях, кроме первого, анализатор, кроме того, должен указать точное значение лексемы: какой именно идентификатор обнаружен, какая константа, скажем, 10 или 1812, какая арифметическая операция, + или * (умножение), какая операция сравнения, = или >. Рассмотрим следующий фрагмент lex-спецификации лексического анализатора для некоторого языка программирования, несколько напоминающего язык Ада:

begin return (BEGIn); end return (END); while return (WHILE); if return (IF); package return (PACKAGE); reverse return (REVERSE); loop return (LOOP); [a-zA-Z][a-zA-Z0-9]* { tokval = put_in_tabl (); return (IDENTIFIER); } [0-9]+ { tokval = put_in_tabl (); return (INTEGER); } \+ { tokval = PLUS; return (ARITHOP); } \- { tokval = MINUS; return (ARITHOP); } > { tokval = GREATER; return (RELOP); } >= { tokval = GREATEREQL; return (RELOP); }


Классы лексем и значения, присваиваемые переменной tokval, обозначены именованными целочисленными константами, что облегчает понимание и модификацию правил. Соответствие между именами и значениями констант устанавливается при помощи операторов #define в C-процедуре разбора. Пример:

#define BEGIn 1 #define END 2 . . . #define PLUS 7 . . .

(В имени BEGIn последняя буква сделана строчной, поскольку имя BEGIN зарезервировано lex'ом.) При использовании yacc'а целесообразно вставить оператор

#include "y.tab.h"

в секцию определений lex-спецификаций. Файл y.tab.h содержит операторы #define, связывающие имена лексем, такие, как BEGIn, END и т.д. с целочисленными значениями, имеющими смысл для сгенерированной процедуры разбора.

В нашем примере для того, чтобы однозначно задать зарезервированное слово, достаточно возвращаемого значения. Для других типов лексем значение лексемы сохраняется в определенной программистом переменной tokval. Эта переменная описывается в секции определений как глобальная, доступная и процедуре лексического анализа, и процедуре синтаксического разбора. yacc для этих же целей предоставляет переменную yylval.

Отметим, что в примере показано два способа установки значения переменной tokval. В соответствии с первым способом функция put_in_tabl() заносит имя и тип идентификатора или константы в таблицу символов, к которой компилятор имеет доступ на этой и следующих стадиях процесса компиляции. Кроме того, put_in_tabl() выдает в качестве результата имя идентификатора или значение константы; этот результат присваивается переменной tokval. Предполагается, что функция put_in_tabl() помещается разработчиком компилятора в секцию подпрограмм. При втором способе, например, в нескольких последних действиях примера, переменной tokval присваивается определенное число, обозначающее, какую операцию распознал анализатор. Если PLUS в соответствии с приведенным выше оператором #define обозначает семерку, то при обнаружении знака + переменной tokval будет присвоено значение 7, характеризующее +. Значение, возвращаемое процедуре разбора, обозначает целый класс операций (в нашем примере - значения ARITHOP и RELOP).




Содержание раздела