Как Flex поддерживает точное расположение зубров? - PullRequest
19 голосов
/ 18 марта 2009

Я пытаюсь использовать flex и bison для создания фильтра, потому что я хочу получить определенные грамматические элементы из сложного языка. Мой план состоит в том, чтобы использовать flex + bison для распознавания грамматики и определения местоположения элементов, представляющих интерес. (Затем используйте скрипт для захвата текста в соответствии с местами сброса.)

Я обнаружил, что flex может поддерживать функцию бизонов, называемую bison-location, но как она работает точно. Я попробовал пример в документе flex, похоже, что yylloc не устанавливается автоматически, я всегда получаю (1,0)-(1,0). Может ли flex рассчитать местоположение каждого токена автоматически? Если нет, то какую интерфейсную функцию мне нужно реализовать? Есть ли какой-нибудь пример?

Есть ли лучшее решение для инструментов?

С наилучшими пожеланиями, Kevin

Edit:

Теперь интерфейс yylex переключается на:

int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );

В руководстве bison не указано, как лексер должен реализовать правильную установку yylloc_param. Мне трудно отследить номер столбца каждого токена вручную.

Ответы [ 8 ]

17 голосов
/ 28 апреля 2011

Объявление yylex, вероятно, изменилось, потому что вы использовали реентерабельный или чистый парсер. Похоже, что многие документы в Интернете предполагают, что это необходимо, если вы хотите, чтобы местоположения бизонов работали, но это не обязательно.

Мне тоже нужны были номера строк, и документация Bison в этом отношении запуталась. Простое решение (с использованием глобальной переменной yylloc): В вашем файле Bison просто добавьте директиву% location:

%{
...
%}
%locations
...
%%
...

в вашем лексере:

%{
...
#include "yourprser.tab.h"  /* This is where it gets the definition for yylloc from */
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%}
%option yylineno
...
%%
...

Макрос YY_USER_ACTION «вызывается» перед каждым из ваших действий с токеном и обновляет yylloc. Теперь вы можете использовать правила @N / @ $ следующим образом:

statement : error ';'   { fprintf(stderr, "Line %d: Bad statement.\n", @1.first_line); }

, или используйте глобальную переменную yylloc:

void yyerror(char *s)
{
  fprintf(stderr, "ERROR line %d: %s\n", yylloc.first_line, s);
}
12 голосов
/ 06 ноября 2011

Мне нравится ответ Шломи.

Кроме того, я также искал обновление местоположения столбца. Найден http://oreilly.com/linux/excerpts/9780596155971/error-reporting-recovery.html, который стал более понятным после прочтения ответа Шломи.

К сожалению, на этой странице есть опечатка для yylloc. Ниже я немного упростил.

В вашем парсере добавьте:

%locations

в вашем лексере:

%{

#include "parser.tab.h"

int yycolumn = 1;

#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno; \
    yylloc.first_column = yycolumn; yylloc.last_column = yycolumn + yyleng - 1; \
    yycolumn += yyleng; \
    yylval.str = strdup(yytext);

%}

%option yylineno

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

Надеюсь, это поможет.

10 голосов
/ 02 марта 2014

Ни bison, ни flex не обновляют yylloc автоматически, но на самом деле нетрудно сделать это самостоятельно - если вы знаете хитрость.

Хитрость в реализации поддержки yylloc заключается в том, что, хотя yyparse() объявляет yylloc, она никогда не меняет ее. Это означает, что если вы измените yylloc в одном вызове лексера, вы найдете те же значения в нем при следующем вызове. Таким образом, yylloc будет содержать позицию последнего токена. Поскольку конец последнего токена совпадает с началом текущего токена, вы можете использовать старое значение yylloc, чтобы помочь вам определить новое значение.

Другими словами, yylex() не должен вычислять yylloc; должно обновить yylloc.

Чтобы обновить yylloc, мы должны сначала скопировать значения last_ в first_, а затем обновить значения last_, чтобы отразить длину только что подобранного токена. (Это не strlen() токена; это длина строк и столбцов.) Мы можем сделать это в макросе YY_USER_ACTION, который вызывается непосредственно перед выполнением любого действия лексера; это гарантирует, что если правило соответствует, но оно не возвращает значение (например, пропущенное правилом пробел или комментарии), местоположение этого не-токена пропускается, а не включается в начало фактического токена, или потерян таким образом, что делает отслеживание местоположения неточным.

Вот версия, предназначенная для реентерабельного парсера; Вы можете изменить его для парентера, не входящего в систему, поменяв операторы -> на .:

#define YY_USER_ACTION \
    yylloc->first_line = yylloc->last_line; \
    yylloc->first_column = yylloc->last_column; \
    for(int i = 0; yytext[i] != '\0'; i++) { \
        if(yytext[i] == '\n') { \
            yylloc->last_line++; \
            yylloc->last_column = 0; \
        } \
        else { \
            yylloc->last_column++; \
        } \
    }

Если вы предпочитаете, вы можете вместо этого поместить этот код в функцию и заставить макрос вызывать функцию, но эти два метода эквивалентны.

7 голосов
/ 18 марта 2009

Взгляните на раздел 3.6 руководства Bison , который, кажется, охватывает некоторые детали. В сочетании с тем, что вы нашли в руководстве по Flex, этого может быть достаточно.

6 голосов
/ 03 октября 2013

Ответ Шоми - самое простое решение, если вам нужно только сохранить номер строки. Однако, если вам также нужны номера столбцов, вам необходимо отслеживать их.

Один из способов сделать это - добавить yycolumn = 1 правил везде, где появляется новая строка (как предложено в ответе Дэвида Элсона), но если вы не хотите отслеживать все места, где может появиться новая строка (пробелы, комментарии, и т. д.) альтернативой является проверка буфера yytext в начале каждого действия:

static void update_loc(){
  static int curr_line = 1;
  static int curr_col  = 1;

  yylloc.first_line   = curr_line;
  yylloc.first_column = curr_col;

  {char * s; for(s = yytext; *s != '\0'; s++){
    if(*s == '\n'){
      curr_line++;
      curr_col = 1;
    }else{
      curr_col++;
    }
  }}

  yylloc.last_line   = curr_line;
  yylloc.last_column = curr_col-1;
}

#define YY_USER_ACTION update_loc();

Наконец, следует отметить, что как только вы начнете отслеживать номера столбцов вручную, вы также можете отслеживать номера строк в одном и том же месте и не беспокоиться об использовании опции Flex yylineno.

4 голосов
/ 02 июня 2012

Итак, я получил это, чтобы «работать», но с парой дополнительных шагов (я мог пропустить их здесь ... извинения в этом случае):

  1. В parser.y , я должен был сказать:

    #define YYLEX_PARAM &yylval, &yylloc
    

    даже с %locations и bison --locations, чтобы заставить его передавать данные.

  2. В lexer.l Мне пришлось использовать -> вместо . для yylloc

  3. Также в lexer.l , я сбрасываю столбец в действии:

    [\n] { yycolumn = 1; }
    

Очевидно, немного сложнее, для \r и т. Д., Но по крайней мере я заставил его работать.

1 голос
/ 13 августа 2015

Думаю, мне удалось заставить его работать (заслуга автора руководства по бизонам ltcalc lexical analyzer ). По умолчанию, bison создает yylloc, который содержит

{ first_line, first_column , last_line , last_column }

Нам нужно только обновить эти значения в нашем лексическом анализаторе. Пример:

[ \t]     { ++yylloc.last_column; }
[\n]      { yyloc.last_column = 0; return EOL; }
[a-zA-Z]+ { 
            yylloc.last_column += strlen(yytext);
            return IDENTIFIER;
          }

Теперь в бизоне, чтобы получить эти поля:

statement : IDENTIFIER '=' expression 
            { printf("%d - %d\n", @1.last_line, @1.last_column); }

По умолчанию эти поля инициализируются в единицу, мы должны инициализировать поля столбцов в ноль, иначе они сообщат о неправильном столбце.

0 голосов
/ 09 ноября 2015

дополнение к ответу Шломи:

Если вы используете% define api.pure в bison для создания реентерабельного парсера, вам также нужно указать% option bison-location в flex. Это связано с тем, что в повторяющемся парсере yylloc не является глобальной переменной и должна передаваться в лексер.

Итак, в парсере:

%define api.pure
%locations

в лексере:

#include "yourprser.tab.h"
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%option bison-locations
%option yylineno
...