Ответ 1
Взгляните на раздел 3.6 руководства Bison - это, по-видимому, охватывает местоположения в деталях. В сочетании с тем, что вы нашли в руководстве Flex, этого может быть достаточно.
Я пытаюсь использовать flex и bison для создания фильтра, потому что хочу получить определенные грамматические элементы из сложного языка. Мой план состоит в том, чтобы использовать flex + bison для распознавания грамматики и сбрасывать местоположение элементов, представляющих интерес. (Затем используйте script для захвата текста в соответствии с местами, сброшенными.)
Я обнаружил, что flex может поддерживать функцию бизона, называемую bison-location, но как она работает точно. Я попробовал этот пример в гибком документе, кажется, что yylloc не устанавливается автоматически flex, я всегда получаю (1,0)-(1,0)
. Можно ли автоматически вычислить местоположение каждого токена? Если нет, какая функция интерфейса определена для меня? Есть ли какой-нибудь пример?
Любое лучшее решение относительно инструментов?
С наилучшими пожеланиями, Кевин
Edit:
Теперь интерфейс для yylex превратится в:
int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );
Руководство bison не указывает, как lexer должен реализовать, чтобы правильно установить yylloc_param. Для меня трудно вручную отслеживать номер столбца каждого токена.
Взгляните на раздел 3.6 руководства Bison - это, по-видимому, охватывает местоположения в деталях. В сочетании с тем, что вы нашли в руководстве Flex, этого может быть достаточно.
Объявление yylex, вероятно, изменилось, потому что вы использовали реентерабельный или чисто-парсер. Похоже, что многие документы по всему сайту предлагают, чтобы вы захотели, чтобы места бизонов работали, но это не требовалось.
Мне тоже нужны номера строк, и я нашел документацию Bison запутанной в этом отношении. Простое решение (с использованием глобального var 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 global var:
void yyerror(char *s)
{
fprintf(stderr, "ERROR line %d: %s\n", yylloc.first_line, s);
}
Мне нравится ответ Шломи.
Кроме того, я искал обновление расположения столбцов. Найдено 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
Возможно, что-то происходит с расположением столбца, которое строго не отслеживает столбцы, а просто продолжает увеличиваться. Это просто мое невежество и пристрастие, если оно кого-то смущает. В настоящее время я использую столбец, чтобы сохранить число символов файла, которое в моем случае более выгодно, чем расположение столбца.
Надеюсь, что это поможет.
Ни bison
, ни flex
не обновляет yylloc
автоматически, но на самом деле это не сложно сделать самому - если вы знаете трюк.
Трюк к реализации поддержки yylloc
заключается в том, что, хотя yyparse()
объявляет yylloc
, он никогда не меняет его. Это означает, что если вы измените yylloc
в одном вызове lexer, вы найдете в нем те же значения при следующем вызове. Таким образом, yylloc
будет содержать позицию последнего токена. Поскольку последний токен совпадает с началом текущего токена, вы можете использовать старое значение yylloc
, чтобы помочь вам определить новое значение.
Другими словами, yylex()
не должен вычислять yylloc
; он должен обновить yylloc
.
Чтобы обновить yylloc
, мы должны сначала скопировать значения last_
в first_
, а затем обновить значения last_
, чтобы отразить длину только что сопоставленного токена. (Это не тег strlen()
, это длина строк и столбцов.) Мы можем сделать это в макросе YY_USER_ACTION
, который вызывается непосредственно перед выполнением любого действия lexer; который гарантирует, что если правило совпадает, но оно не возвращает значение (например, правило, пропускающее пробелы или комментарии), местоположение этого не-токена пропускается, а не включается в начале фактического токена, или потерянный таким образом, что делает отслеживание местоположения неточным.
Здесь версия, предназначенная для повторного анализатора; вы можете изменить его для не-реентерабельного анализатора, заменив операторы ->
на .
:
#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++; \
} \
}
Если вы предпочитаете, вместо этого вы можете поместить этот код в функцию и сделать макрокоманду функцией, но эти два метода эквивалентны.
Ответ Shomi - это самое простое решение, если вы только заботитесь о сохранении номера строки. Однако, если вам также нужны номера столбцов, вам необходимо их отслеживать.
Один из способов сделать это - добавить правила 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
.
Итак, я получил это, чтобы "работать", но с несколькими дополнительными шагами (я мог упустить их здесь... извинения в этом случае):
В parser.y я должен был сказать:
#define YYLEX_PARAM &yylval, &yylloc
даже с %locations
и bison --locations
, чтобы заставить его передать данные.
В lexer.l мне пришлось использовать ->
вместо .
для yylloc
Также в lexer.l, я reset столбец в действии:
[\n] { yycolumn = 1; }
Очевидно, немного сложнее, для \r
и т.д., но по крайней мере я получил его для работы.
Я думаю, мне удалось заставить его работать (кредит принадлежит писателю справочника bison Лексический анализатор ltcalc). По умолчанию бизон создает 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); }
По умолчанию эти поля инициализируются одним, мы должны инициализировать поля столбца до нуля, иначе они сообщают о некорректном столбце.
В дополнение к ответу Шломи:
Если вы используете% define api.pure в bison для создания реентераторного парсера, вам также нужно указать% bison-locations в опции flex. Это связано с тем, что в реентерабельном парсере yylloc не является глобальной переменной и должен быть передан в lexer.
Итак, в синтаксическом анализаторе:
%define api.pure
%locations
в лексере:
#include "yourprser.tab.h"
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%option bison-locations
%option yylineno