Bison 1.35 - 5. Интерфейс анализатора на C

[Содержание]   [Назад]   [Пред]   [Вверх]   [След]   [Вперед]  


5. Интерфейс анализатора на C

Анализатор Bison -- это на самом деле функция на C, называющаяся yyparse. Здесь мы опишем соглашения по интерфейсу yyparse и других необходимых ей функций.

Имейте в виду, что для своих внутренних целей анализатор использует множество идентификаторов C, начинающихся с `yy' и `YY'. Если вы используете такой идентификатор (кроме тех, что описаны в настоящем руководстве) в действии или дополнительном коде на C в файле грамматики, вероятно, вы столкнётесь с неприятностями.

5.1 Функция анализатора yyparse

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

Если разбор завершён успешно (возврат вызван концом входного текста), yyparse возвращает значение 0.

Значение 1 возвращается, если разбор не удался (возврат вызван синтаксической ошибкой).

В действии вы можете потребовать немедленного возврата из yyparse, используя следующие макросы:

YYACCEPT
Немедленный возврат со значением 0 (сообщение об удачном разборе).
YYABORT
Немедленный возврат со значением 1 (сообщение об ошибке).

5.2 Функция лексического анализатора yylex

Функция лексического анализатора yylex распознаёт лексемы во входном потоке и передаёт их анализатору. Bison не создаёт эту функцию автоматически, вы должны написать её так, чтобы yyparse могла вызывать её. Эту функцию иногда называют лексическим сканером.

В простых программах yylex часто определяется в конце файла грамматики Bison. Если yylex определена в отдельном исходном файле, вам нужно сделать доступными там макроопределения типов лексем. Для этого используйте параметр `-d' при запуске Bison, чтобы он записал эти макроопределения в отдельный файл заголовка `имя.tab.h', который вы можете включить в другие исходные файлы, которым он нужен. См. раздел 10. Вызов Bison.

5.2.1 Соглашения о вызове yylex

Значение, возвращаемое yylex должно быть числовым кодом типа только что встреченной лексемы, или 0 для обозначени конца входного текста.

Если в правилах грамматики на лексему ссылаются по имени, это имя становится в файле анализатора макросом C, определением которого будет числовой код, соответствующий этому типу лексемы. Таким образом, yylex может использовать для обозначения типа это имя. См. раздел 4.2 Символы, терминальные и нетерминальные.

Если в правилах грамматики на лексему ссылаются с помощью однолитерной константы, числовой код этой литеры также является кодом типа лексемы. Таким образом yylex может просто вернуть код этой литеры. Нулевая литера не должна использоваться таким образом, потому что её код -- ноль, означающий конец входного текста.

Приведём пример, иллюстрирующий это:

int
yylex (void)
{
  ...
  if (c == EOF)     /* Проверка конца файла. */
    return 0;
  ...
  if (c == '+' || c == '-')
    return c;      /* Полагаем, что тип лексемы `+' -- '+'. */
  ...
  return INT;      /* Вернуть тип лексемы. */
  ...
}

Этот интерфейс разрабатывался так, чтобы выход утилиты lex мог быть без изменений использован как определение yylex.

Если грамматика использует строковые лексемы, есть два способа, которыми yylex может определить коды их типов лексем:

  • Если грамматика определяет символические имена лексем как псевдонимы строковых лексем, yylex может использовать эти символические имена как и все остальные. В этом случае использование строковых лексем в файле грамматики не окажет влияния на yylex.
  • yylex может найти многолитерную лексему в таблице yytname. Индекс лексемы в этой таблице -- это код типа лексемы. Имя многолитерной лексемы записывается в yytname в виде: двойная кавычка, литеры лексемы, вторая двойная кавычка. Литеры лексемы никаким образом не экранируются, они дословно переносятся в содержимое строки в таблице. Приведём код поиска лексемы в yytname, полагая, что литеры лексемы находятся в массиве token_buffer.
    for (i = 0; i < YYNTOKENS; i++)
      {
        if (yytname[i] != 0
            && yytname[i][0] == '"'
            && strncmp (yytname[i] + 1, token_buffer,
                        strlen (token_buffer))
            && yytname[i][strlen (token_buffer) + 1] == '"'
            && yytname[i][strlen (token_buffer) + 2] == 0)
          break;
      }
    
    Таблица yytname создаётся только если вы используете объявление %token_table. См. раздел 4.7.8 Обзор объявлений Bison.

5.2.2 Семантические значения лексем

В обычном (не повторно входимом) анализаторе семантические значения лексем должны помещаться в глобальную переменную yylval. Если вы используете единственный тип данных для семантических значений, yylval имеет этот тип. Так, если этот тип int (по умолчанию), вы можете написать в yylex:

  ...
  yylval = value;  /* Поместить значение на вершину стека Bison. */
  return INT;      /* Вернуть тип лексемы. */
  ...

Если вы используете множественные типы данных, тип yylval -- объединение типов, полученное из объявления %union (см. раздел 4.7.3 Набор типов значений). Так, если вы сохраняете значение лексемы, вы должны использовать правильный элемент объединения. Если объявление %union выглядит так:

%union {
  int intval;
  double val;
  symrec *tptr;
}

то код в yylex может выглядеть так:

  ...
  yylval.intval = value; /* Поместить значение на вершину */
                         /* стека Bison. */
  return INT;          /* Вернуть тип лексемы. */
  ...

5.2.3 Позиции лексем в тексте

Если вы используете в действиях `@n'-свойства (см. раздел 4.6 Отслеживание положений) для отслеживания положений лексем и групп в тексте, ваша функция yylex должна предоставить эту информацию. Функция yyparse ожидает, что положение только что разобранной лексемы в тексте находится в глобальной переменной yylloc. Таким образом, yylex должна поместить в эту переменную правильные данные.

По умолчанию значение yylloc -- это структура, и вам нужно только проинициализировать её элементы, которые вы собираетесь использовать в действиях. Эти четыре элемента называются, first_line, first_column, last_line и last_column. Отметим, что использование этих свойств делает анализатор заметно более медленным.

Тип данных yylloc называется YYLTYPE.

5.2.4 Соглашения о вызове для чистых анализаторов

Если вы используете объявление Bison %pure_parser, требующее создания чистого, повторно входимого анализатора, глобальные переменные взаимодействия yylval и yylloc использовать нельзя (см. раздел 4.7.7 Чистый (повторно входимый) анализатор). В таких анализаторах эти две глобальные переменные замещаются указателями, передаваемыми в качестве аргументов функции yylex. Вы должны объявить их, как здесь показано, и передавать информацию назад, помещая её по этим указателям.

int
yylex (YYSTYPE *lvalp, YYLTYPE *llocp)
{
  ...
  *lvalp = value;  /* Поместить значение на вершину стека Bison.  */
  return INT;      /* Вернуть тип лексемы.  */
  ...
}

Если файл грамматики не использует конструкции `@' для ссылок на позиции в тексте, тип YYLTYPE не будет определён. В этом случае опустите второй аргумент, yylex будет вызываться только с одним аргументом.

Если вы используете повторно входимый анализатор, вы можете (необязательно) передавать ему информацию о дополнительных параметрах повторно входимым способом. Для этого определите макрос YYPARSE_PARAM как имя переменной. Это изменит функцию yyparse чтобы она принимала один аргумент с этим именем типа void *.

При вызове yyparse передайте адрес объекта, приведя его к типу void *. Действия грамматики могут ссылаться на сожержимое объекта, приводя значение указателя обратно к его правильному типу, и затем разыменовывая его. Приведём пример. Напишите в анализаторе:

%{
struct parser_control
{
  int nastiness;
  int randomness;
};

#define YYPARSE_PARAM parm
%}

Затем вызовите анализатор следующим образом:

struct parser_control
{
  int nastiness;
  int randomness;
};

...

{
  struct parser_control foo;
  ...  /* Поместить правильные данные в foo.  */
  value = yyparse ((void *) &foo);
  ...
}

В действиях грамматики используйте для обращения к данным выражения наподобие следующего:

((struct parser_control *) parm)->randomness

Если вы хотите передать данные дополнительных параметров функции yylex, определите макрос YYLEX_PARAM тем же способом, что и для YYPARSE_PARAM, как показано ниже:

%{
struct parser_control
{
  int nastiness;
  int randomness;
};

#define YYPARSE_PARAM parm
#define YYLEX_PARAM parm
%}

Затем вам следует определить yylex, чтобы она принимала дополнительный аргумент -- значение parm (всего будет два или три аргумента, в зависимости от того, передаётся ли аргумент типа YYLTYPE). Вы можете объявить аргумент как указатель на правильный тип объекта, или же объявить его как void * и получать доступ к содержимому как показано выше.

Вы можете использовать `%pure_parser' и потребовать создания повторно входимого анализатора, не используя при этом YYPARSE_PARAM. Тогда вам следует вызывать yyparse без аргументов, как обычно.

5.3 Функция сообщения об ошибках yyerror

Анализатор Bison обнаруживает ошибку разбора или синтаксическую ошибку каждый раз, когда читает лексему, которая не может удовлетворять никакому синтаксическому правилу. Действие в грамматике может также явно сообщить об ошибке, используя макрос YYERROR (см. раздел 5.4 Специальные возможности, используемые в действиях).

Анализатор Bison рассчитывает сообщить об ошибке, вызывая функцию сообщения об ошибке yyerror, которую должны предоставить вы. Она вызывается функцией yyparse каждый раз при обнаружении синтаксической ошибки, и принимает один аргумент. В случае ошибки разбора это обычно строка "parse error".

Если вы определите макрос YYERROR_VERBOSE в секции объявлений Bison (см. раздел 4.1.2 Секция объявлений Bison), Bison будет давать более подробные и обстоятельные строки сообщений об ошибках, вместо обычного "parse error". Не имеет значения, какое определение вы используете для YYERROR_VERBOSE, только то, определили ли вы его.

Анализатор может обнаружить ещё один тип ошибки -- переполнение стека. Это происходит, когда входной текст содержит конструкции слишком большой глубины вложенности. Маловероятно, что вы столкнётесь с этим, поскольку анализатор Bison расширяет свой стек автоматически до очень больших пределов. Но если переполнение всё же происходит, yyparse вызывает обычным образом yyerror, за исключением того, что аргументом будет строка "parser stack overflow".

Следующего определения достаточно для простых программ:

void
yyerror (char *s)
{
  fprintf (stderr, "%s\n", s);
}

После возвращения из yyerror в yyparse последняя попытается произвести восстановление после ошибки, если в грамматике вы написали подходящие правила восстановления после ошибок (см. раздел 7. Восстановление после ошибок). Если восстановление невозможно, yyparse немедленно завершит работу, вернув 1.

Переменная yynerrs содержит число обнаруженных до сих пор синтаксических ошибок. Обычно эта переменная глобальная, но если вы требуете создания чистого анализатора (см. раздел 4.7.7 Чистый (повторно входимый) анализатор), это локальная переменная, к которой могут иметь доступ только действия.

5.4 Специальные возможности, используемые в действиях

Ниже представлена таблица конструкций Bison, переменных и макросов, которые могут быть полезны в действиях.

`$$'
Играет роль переменной, содержащей семантическое значение группы, собираемой текущим правилом. См. раздел 4.5.3 Действия.
`$n'
Играет роль переменной, содержащей семантическое значение n-го компонента текущего правила. См. раздел 4.5.3 Действия.
`$<тип_альт>$'
Аналогична $$, но задаёт альтернативу тип_альт в объединении, заданном объявлением %union. См. раздел 4.5.4 Типы данных значений в действиях.
`$<тип_альт>n'
Аналогична n, но задаёт альтернативу тип_альт в объединении, заданном объявлением %union. См. раздел 4.5.4 Типы данных значений в действиях.
`YYABORT;'
Немедленно завершает работу yyparse, сообщая об ошибке. См. раздел 5.1 Функция анализатора yyparse.
`YYACCEPT;'
Немедленно завершает работу yyparse, сообщая об удачном разборе. См. раздел 5.1 Функция анализатора yyparse.
`YYBACKUP (лексема, значение);'
Отмена сдвига лексемы. Этот макрос допустим только в правилах, которые выполняют свёртку единственного значения, и только когда нет предпросмотренной лексемы. Он устанавливает для предпросмотренной лексемы тип лексема и семантическое значение значение. Затем он отбрасывает значение, которое должно быть свёрнуто по этому правилу. Если макрос используется, когда его применение недопустимо, как например, когда уже есть предпросмотренная лексема, он сообщает о синтаксической ошибке сообщением `cannot back up' и производит обычное восстановление после ошибки. В любом случае оставшаяся часть правила не выполняется.
`YYEMPTY'
Значение, помещаемое в yychar, когда там нет предпросмотренной лексемы.
`YYERROR;'
Немедленно вызывает синтаксическую ошибку. Этот оператор запускает восстановление после ошибки, как если бы ошибку обнаружил сам анализатор, и не выводит никакого сообщения. Если вы хотите вывести сообщение об ошибке, перед оператором `YYERROR;' вызовите явно yyerror. См. раздел 7. Восстановление после ошибок.
`YYRECOVERING'
Этот макрос заменяет выражение, имеющее значение 1 когда анализатор выполняет восстановление после синтаксической ошибки, и 0 всё остальное время. См. раздел 7. Восстановление после ошибок.
`yychar'
Переменная, содержащая текущую предпросмотренную лексему (в чистом анализаторе это на самом деле локальная для yyparse переменная). Когда предпросмотренной лексемы нет, в неё помещается значение YYEMPTY. См. раздел 6.1 Предпросмотренные лексемы.
`yyclearin;'
Отбросить текущую предпросмотренную лексему. Это полезно, прежде всего, в правилах обработки ошибок. См. раздел 7. Восстановление после ошибок.
`yyerrok;'
Немедленно взобновляет создание сообщений об ошибках для последующих синтаксических ошибок. Это полезно, прежде всего, в правилах обработки ошибок. См. раздел 7. Восстановление после ошибок.
`@$'
Играет роль структурной переменной, содержащей информацию о позиции в тексте группы, создаваемой текущим правилом. См. раздел 4.6 Отслеживание положений.
`@n'
Играет роль структурной переменной, содержащей информацию о позиции в тексте n-го компонента текущего правила. См. раздел 4.6 Отслеживание положений.


[Содержание]   [Назад]   [Пред]   [Вверх]   [След]   [Вперед]