Подразделы

Другие разделы

Дата и время

25/04/2024 11:59:46

Авторизация

Имя:
Пароль:
Зарегистрироваться
Восстановить пароль
 

printРазработка компиляторов и интерпретаторов

printПринципы работы BISON, интеграция BISON и FLEX, обработка синтаксических ошибок

По описанию BISON генерирует автомат с магазинной памятью. В стеке хранятся символы грамматики и ассоциированные с ними значения, полученные в процессе разбора или из лексического анализатора (токены). Работа автомата определяется набором таблиц, связывающих текущее состояние стека и очередной входной токен с действием автомата. Возможно только два варианта действия:
  • свёртка (reduce) – применение одного из правил грамматики и превращение нескольких верхних символов в нетерминальный символ-результат (при этом выполняется код, указанный после правила);
  • сдвиг (shift) – помещение токена на стек и ввод следующего токена.
Первоначально стек пуст. После завершения разбора в стеке должен находиться стартовый символ грамматики.
Если написанный вами набор правил некорректен, возможны два вида конфликтов:
  • свёртка/свёртка – есть несколько правил, которые можно применить для некоторой последовательности символов в стеке;
  • сдвиг/свёртка – существует как правило, которое можно применить, так и правило, в котором после последовательности символов из первого правила есть другие символы.
От конфликтов свёртка/свёртка нужно обязательно избавляться, но небольшое количество конфликтов сдвиг/свёртка может присутствовать из-за неоднозначности синтаксиса языка. Например, следующие правила дадут 1 конфликт сдвиг/свёртка:
operator : 
  | IF expr THEN operator
  | IF expr THEN operator ELSE operator
  | IDENT '=' expr
  ;
При наличии такого конфликта всегда выполняется сдвиг, поэтому конструкция "if a then if b then c=x else c=y" интерпретируется как "if a then {if b then c=x else c=y}", а не как "if a then {if b then c=x} else c=y". Обычно количество таких конфликтов не должно превышать 1-2, и чтобы BISON не давал предупреждений при обработке правил для подобных конструкций, применяется опция %expect n, где n – ожидаемое количество конфликтов сдвиг/свёртка.
Из описания BISON генерирует функцию yyparse(), которая для получения токена вызывает функцию yylex(). Функция yylex() должна вернуть тип лексемы (целое число), а ассоциированное с ней значение поместить в глобальную переменную yylval (или в параметр функции, если установлена опция %define api.pure full). Именованные константы для токенов определяются в описании для BISON с помощью %token, и чтобы они стали доступны в программе, сгенерированной FLEX, необходимо создать заголовочный файл и подключить его в описании лексического анализатора. Для этого в командной строке при генерации необходимо указать ключ -d:
bison -oy.tab.cpp -d my.grm
Первый ключ -o указывает имя файла для сгенерированного модуля, и в зависимости от расширения выходного файла будет создан заголовочный файл y.tab.hpp или y.tab.h. В заголовочный файл также попадает объявление типа-объединения и глобальной переменной yylval. Но информация о соответствии токенов с полями типа-объединения не будет доступна. Поэтому при помещении значения токена в yylval необходимо указывать имя поля явно.
По умолчанию функции yyerror передается сообщение "syntax error". Для более подробной информации нужно указать опцию
%define parse.error verbose
По умолчанию функция yyparse завершает разбор после обнаружения синтаксической ошибки, но можно продолжить анализ входного текста, чтобы найти другие ошибки или выполнить следующие команды в интерактивном интерпретаторе. Для этого используется специальный символ error. Пи ошибке разбора вызывается функция yyerror и выполняется поиск подходящего правила с error, из стека разбора удаляются верхние элементы, пока его состояние не будет соответствовать состоянию, при котором возможно добавление символа error, затем будут пропущены входные токены, пока не будет найден токен, указанный после символа error, или токен, который может встретиться после символа-результата правила с error. После применения правила для ошибки разбор будет продолжен, но новые сообщения об ошибке не выводятся, пока не будет выполнена хотя бы одна свёртка без error. Вы можете контролировать количество обнаруженных ошибок в функции yyerror. В действии для правила с error можно использовать вспомогательные макросы (всегда в паре!): yyerrok (обработка ошибки завершена и можно сообщать о следующей синтаксической ошибке) и yyclearin (перейти к следующему токену).
Пример программы
Лексический анализатор calc.lex:
%option noyywrap interactive bison
%{
#include "y.tab.h"
#include <stdio.h>
int findid(const char *);
%}
%%
[a-zA-Z][a-zA-Z0-9_]* { 
           yylval=findid(yytext);8
           return IDENT;
        }
[0-9]+ { 
      sscanf(yytext,"%d",&yylval); 
      return NUMBER;
     }
[\t ] ;
\n|. return yytext[0];
%%
Синтаксический анализатор calc.grm:
%start program
%token NUMBER "число" IDENT "идентификатор"
%left '+' '-'
%left '*' '/'
%left UMINUS
%define parse.error verbose
%{
#include <stdio.h>
int mem[1000];
void yyerror(const char *);
int yylex();
%}
%%
program: 
   | program operator '\n'
   ;
operator: 
     IDENT '=' expr { mem[$1]=$3; printf("%d\n",mem[$1]);} 
   | expr { printf("%d\n",$1);} 
   | error 
   ;
expr: 
   expr '+' expr { $$ = $1+$3; }
 | expr '-' expr { $$ = $1-$3; }
 | expr '*' expr { $$ = $1*$3; }
 | expr '/' expr { $$ = $1/$3; }
 | '-' expr %prec UMINUS  { $$ = -$2; }
 | '(' expr ')' { $$ = $2; }
 | NUMBER { $$ = $1; }
 | IDENT { $$ = mem[$1]; }
 ;
Вспомогательные функции calc.cpp:
#include <string>
#include <map>
using namespace std;
map<string,int> ti;
int yyparse();
extern int yylineno;
int findid(const char *yytext)
{  if(ti.find(yytext)==ti.end()) 
   { int k=ti.size();
     ti[yytext]=k;
     return k; 
   }
   return ti[yytext];
}
void yyerror(const char *msg)
{  fprintf(stderr,"%d: %s\n",yylineno, msg);
}
int main()
{  return yyparse();
}
Скачать архив с примером

Недостатки инструментов
В BISON нет поддержки РБНФ, дерево разбора не создается (нужно использовать дополнительные инструменты) и невозможно дать русские названия символам грамматики.
Возможность указать синоним для терминальных символов приближает правила грамматики к РБНФ и улучшает вывод информации о синтаксических ошибках. Но в РБНФ кавычки используются только для терминальных символов-литералов, а прочие терминальные символы должны записываться в ??. В результате в BISON нет визуальной разницы между литералами и прочими терминальными символами. Кроме того в РБНФ литералы можно писать в кавычках и апострофах по своему усмотрению, в BISON разрешено использовать апострофы только для односимвольных литералов. Для всех терминальных символов, которые не являются литералом из одного символа, необходимо придумать название, которое используется только во FLEX, а способ преобразования лексемы в номер типа лексемы, предлагаемый разработчиками BISON, неудобный и неэффективный.
FLEX не знает о соответствии между полями типа-объединения и терминальными символами. Синтаксис обращения к значению символа во FLEX и BISON отличается – вместо $$ необходимо писать yylval.имяполя.
Для каждого инструмента необходимо создавать отдельный файл с описанием. При этом в генерируемом коде нет даже объявлений для тех функций, которые в нем используются (BISON – yylex, FLEX – yyerror) и необходимо их добавлять самостоятельно в заголовочный файл.
loading