flex / bison: как переключить два лексера в одном входном файле

Как я могу передать открытый файл, например читать другим сканером в следующий сканер - и отдавать парсеру?


person sqller    schedule 25.08.2016    source источник
comment
Почему? Просто нажмите стартовое состояние, чтобы переключиться на другой набор правил в том же сканере, и вставьте его, когда захотите выйти из них.   -  person user207421    schedule 26.08.2016
comment
Пожалуйста, подтвердите ответ ричи.   -  person akim    schedule 19.09.2016


Ответы (1)


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

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

Поскольку сканеры Flex основаны на таблицах, объединение двух сканеров не является неэффективным; действительно, может быть некоторая ценность в том, чтобы не дублировать код. Объединенная таблица может быть немного больше, чем сумма отдельных таблиц, потому что, вероятно, будет больше классов эквивалентности символов, но, с другой стороны, большая таблица может обеспечить лучшее сжатие таблицы. Ни один из этих эффектов вряд ли будет заметен.


Вот простой, но, возможно, полезный пример. Этот синтаксический анализатор читает файл и заменяет ${arithmetic expressions} вычисленным выражением. (Поскольку это всего лишь пример, разрешены только очень простые выражения, но их должно быть легко расширить.)

Поскольку лексический сканер должен запускаться в состоянии запуска SC_ECHO, его необходимо инициализировать. Лично я бы предпочел начать с INITIAL, чтобы избежать этой инициализации в этом простом случае, но иногда сканерам необходимо уметь обрабатывать различные условия запуска, поэтому я оставил код. Обработку ошибок можно улучшить, но она работает.

Синтаксический анализатор использует очень простое error правило для повторной синхронизации и отслеживания ошибок подстановки. Семантическое значение нетерминалов subst, file и start - это счетчик ошибок для файла; семантическое значение для expr - это значение выражения. В этом простом случае они оба являются просто целыми числами, поэтому работает тип по умолчанию для yylval.

Незавершенные замены не обрабатываются изящно; в частности, если EOF читается во время лексического сканирования для замены, в вывод не вставляется никакая индикация. Я оставляю это как упражнение. :)

Вот лексер:

%{
#include "xsub.tab.h"
%}
%option noinput nounput noyywrap nodefault
%option yylineno
%x SC_ECHO
%%
   /* In a reentrant lexer, this would go into the state object */
   static int braces;

   /* This start condition just echos until it finds ${... */
<SC_ECHO>{
  "${"        braces = 0; BEGIN(INITIAL);
  [^$\n]+     ECHO;
  "$"         ECHO;
  \n          ECHO;
}
 /* We need to figure out where the substitution ends, which is why we can't
  * just use a standard calculator. Here we deal with terminations.
  */
"{"           ++braces; return '{';
"}"           { if (braces) { --braces; return '}'; }
                else        { BEGIN(SC_ECHO); return FIN; }
              }

 /* The rest is just a normal calculator */
[0-9]+        yylval = strtol(yytext, NULL, 10); return NUMBER;
[[:blank:]]+  /* Ignore white space */
\n            /* Ignore newlines, too (but could also be an error) */
.             return yytext[0];

%%
void initialize_scanner(void) {
  BEGIN(SC_ECHO);
}

Парсер экспортирует единый интерфейс:

int parseFile(FILE *in, *out);

который возвращает 0, если все прошло хорошо, и в противном случае количество неправильных замен (по модулю упомянутой выше проблемы с незавершенными заменами). Вот файл:

%{
#include <stdio.h>
int yylex(void);
void yyerror(const char* msg);
void initialize_scanner(void);

extern int yylineno;
extern FILE *yyin, *yyout;
%}
%token NUMBER FIN UNOP
%left '+' '-'
%left '*' '/' '%'
%nonassoc UNOP

%define parse.lac full
%define parse.error verbose
%%
start: file          { if ($1) YYABORT; else YYACCEPT; }
file :               { $$ = 0; }
     | file subst    { $$ = $1 + $2; }
subst: expr FIN      { fprintf(yyout, "%d", $1); $$ = 0; }
     | error FIN     { fputs("${ BAD SUBSTITUTION }", yyout); $$ = 1; }
expr : NUMBER
     | '-' expr %prec UNOP { $$ = -$2; }
     | '(' expr ')'  { $$ = $2; }
     | expr '+' expr { $$ = $1 + $3; }
     | expr '-' expr { $$ = $1 - $3; }
     | expr '*' expr { $$ = $1 * $3; }
     | expr '/' expr { $$ = $1 / $3; }
     | expr '%' expr { $$ = $1 % $3; }
%%
void yyerror(const char* msg) {
  fprintf(stderr, "%d: %s\n", yylineno, msg);
}

int parseFile(FILE* in, FILE* out) {
  initialize_scanner();
  yyin = in;
  yyout = out;
  return yyparse();
}

И простой драйвер:

#include <stdio.h>
int parseFile(FILE* in, FILE* out);
int main() {
  return parseFile(stdin, stdout);
}
person rici    schedule 25.08.2016
comment
Спасибо за ответ ! Вопрос возник, потому что я хотел бы перечитать что-то вроде заголовка, но в настоящее время он включает некоторые ключевые слова, которые распознаются вторым сканером. Как я могу изменить правила контекстно-зависимого сканера? без попытки определить синтаксис заголовка, который следует опустить. Может быть, это из-за регулярных выражений, когда у меня возникают проблемы, если я хочу что-то пропустить, но позже мне нужно назначить токены разделителям и идентификаторам ... - person sqller; 26.08.2016
comment
нужно больше места: конечно, я могу вручную распознать заголовок - а затем передать его второму сканеру и парсеру - это работает - но я хотел, чтобы язык в файлах lex был разделен, а не закодирован жестко. - person sqller; 26.08.2016
comment
ах - не знал об условиях запуска сканера - и их назначении. Подумал больше о стартовом символе в продакшене / парсере. Попробую - когда я лучше пойму, как с ними бороться. - person sqller; 26.08.2016
comment
@sqller: добавлен пример и помечены ваши неответы как неответы. На SO ответ не является расширенной формой комментария; вам следует либо отредактировать свой вопрос (если вы предоставляете дополнительную информацию о своем исходном вопросе), либо задать новый вопрос (если у вас есть другой вопрос). - person rici; 28.08.2016
comment
Хорошо, теперь я понял. Я могу написать функцию, которая в моем случае вызывает BEGIN (ignore), или использовать другое правило lex для установки начального условия. Но я не должен инициализировать начальное условие поверх всех определений. Спасибо всем, кто помог мне разобраться в механизме. (Здесь я также не знал, что могу вызвать BEGIN в кодировке) - person sqller; 28.08.2016
comment
@rici Не могли бы вы привести несколько примеров для проверки всех ветвей лексера / грамматики. Я пытаюсь понять лексический анализатор / грамматику. С некоторыми примерами это будет здорово. - person user1424739; 15.02.2019
comment
@rici Например, какой правильный ввод запускает ++braces. Я использовал несколько {, но в итоге получаю ошибки. - person user1424739; 15.02.2019
comment
@ user1424739: лексический анализатор и синтаксический анализатор не связаны тесно. С этим синтаксическим анализатором ни один допустимый ввод не может включать вложенные фигурные скобки. Но если вы добавите в грамматику выражений синтаксис с фигурными скобками, лексер будет работать без изменений. На самом деле, это всего лишь приблизительный набросок того, как создать приложение. Полное приложение с полным набором функций было бы слишком большим, чтобы дать ответ SO, а его сложность скроет простую концепцию, которую этот ответ пытается объяснить. - person rici; 15.02.2019