Каждый вызов `yylex ()` генерирует токен или все токены для ввода? - PullRequest
0 голосов
/ 07 февраля 2019

Я пытаюсь понять, как работает flex под капотом.

  • В следующем первом примере кажется, что main() вызывает yylex() только один раз, а yylex() генерирует все токены для всего входа.

  • Во втором примере кажется, что main() вызывает yylex() один раз для каждого сгенерированного токена, а yylex() генерирует токен для вызова.

Генерирует ли каждый вызов yylex() токен или все токены для входа?

Почему в этих двух примерах yylex() вызывается разное количество раз?

Я слышал, что yylex() походит на сопрограмму, и каждый вызов к нему возобновляется с остальной частью ввода, оставшейся от последнего вызова, и генерирует токен.В этом смысле, как первый пример вызывает yylex() только один раз и генерирует все токены на входе?

/* just like Unix wc */
%{
int chars = 0;
int words = 0;
int lines = 0;
%}
%%
[a-zA-Z]+  { words++; chars += strlen(yytext); }
\n         { chars++; lines++; }
.          { chars++; }
%%
main(int argc, char **argv)
{
  yylex();
  printf("%8d%8d%8d\n", lines, words, chars);
}


$ ./a.out
The boy stood on the burning deck
shelling peanuts by the peck
^D
2 12 63
$

и

/* recognize tokens for the calculator and print them out */
%{
   enum yytokentype {
     NUMBER = 258,
     ADD = 259,
     SUB = 260,
     MUL = 261,
     DIV = 262,
     ABS = 263,
     EOL = 264
   };
   int yylval;
%}
%%
"+"    { return ADD; }
"-"    { return SUB; }
"*"    { return MUL; }
"/"    { return DIV; }
"|"    { return ABS; }
[0-9]+ { yylval = atoi(yytext); return NUMBER; }
\n     { return EOL; }
[ \t]  { /* ignore whitespace */ }
.      { printf("Mystery character %c\n", *yytext); }
%%
main(int argc, char **argv)
{
  int tok;
  while(tok = yylex()) {
    printf("%d", tok);
    if(tok == NUMBER) printf(" = %d\n", yylval);
    else printf("\n");
  }
}

$ ./a.out
a / 34 + |45
Mystery character a
262
258 = 34
259
263
258 = 45
264
^D
$

1 Ответ

0 голосов
/ 07 февраля 2019

Flex не определяет, когда вернется сканер (за исключением правила EOF по умолчанию).Сканер, который он создает, выполняет лексические действия в цикле, пока какое-либо действие не вернется.Так что вам решать, как вы хотите структурировать свой сканер.

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

В вашем первом примере кода нет парсера, и действие сканера ограничено распечаткой токена.Хотя пример совершенно правильный, полагаясь на цикл сканера для многократного выполнения действий, я бы предпочел вторую модель, даже если вы (пока) не намерены добавлять парсер, потому что это облегчит разделение обработки токена отгенерация токенов.

Это не значит, что каждое лексическое действие будет содержать оператор return.Некоторые лексические шаблоны соответствуют не токенам (например, комментарии и пробелы), и соответствующее действие, скорее всего, ничего не изменит (кроме возможной записи входной позиции), так что сканер продолжит поиск токена для возврата.

(F) Сканеры lex нелегко превратить в сопрограммы, поэтому, если действительно требуется сопрограмма (например, для пошагового разбора асинхронного ввода), тогда может быть предпочтительнее другой инструмент.

Bisonдействительно дает возможность генерировать «синтаксический анализатор», в котором сканер вызывает анализатор каждый раз, когда находит токен, вместо возврата к анализатору.Но ни «push», ни традиционная «pull» модель не имеют ничего общего с сопрограммами, ИМХО, и использование слова для описания взаимодействия анализатора и сканера кажется мне неточным и бесполезным (хотя я очень уважаюавтор, которого вы цитируете.)

...