Неожиданное поведение сгенерированного парсера bison

Я создал синтаксический анализатор через Flex/Bison, который неожиданно терпит неудачу во время синтаксического анализа. Вот упрощенный пример, который показывает проблему

Лексер.л:

%{
#include "Parser.h"
%}
%option noyywrap nodefault

%%
"foo"               {   return FOO;                          }
"bar"               {   return BAR;                          }
"("                 {   return OP;                           }
")"                 {   return CP;                           }
[ \t\n]+            {   /*    DO NOTHING  */                 }
.                   {   YY_FATAL_ERROR("unknown character"); }
%%

И Parser.y (с включенной трассировкой и многословием):

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

%debug
%verbose
%error-verbose
%token FOO BAR OP CP

%%
program_expr :   foo_expr bar_expr   {}
;
foo_expr :   /*  NOTHING  */  {}
    |   OP FOO CP           {}
;
bar_expr :   /*  NOTHING  */  {}
    |   OP BAR CP           {}
;
%%

int main(int argc, char** argv)
{
    yydebug = 1;
    yyparse();
    return 0;
}

void yyerror (char const *s) {  fprintf(stderr, "%s\n", s); }

Но сгенерированный синтаксический анализатор потерпит неудачу, если я укажу ввод, например (bar) - дерево синтаксического анализа в этом случае должно содержать выражение foo, которое пусто. Он сообщает:

Starting parse

Entering state 0

Reading a token: Next token is token OP ()

Shifting token OP ()

Entering state 1

Reading a token: Next token is token BAR ()

syntax error, unexpected BAR, expecting FOO

Error: popping token OP ()

Stack now 0

Cleanup: discarding lookahead token BAR ()

Stack now 0

Вот фрагмент текста из сгенерированного описания shift/reduce automata:

state 0
    0 $accept: . program_expr $end
    OP  shift, and go to state 1
    OP        [reduce using rule 2 (foo_expr)]
    $default  reduce using rule 2 (foo_expr)
    program_expr  go to state 2
    foo_expr      go to state 3


state 1
    3 foo_expr: OP . FOO CP
    FOO  shift, and go to state 4
state 2
    0 $accept: program_expr . $end
    $end  shift, and go to state 5
state 3
    1 program_expr: foo_expr . bar_expr
    OP  shift, and go to state 6
    $default  reduce using rule 4 (bar_expr)
    bar_expr  go to state 7

Но я не могу понять смысл/синтаксис таких состояний. Что не так с моей грамматикой/парсером?

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

sepp2k 29.05.2019 14:16

@sepp2k, извини. Решил проверить, вывод есть Parser.y: conflicts: 1 shift/reduce исправлю этот вопрос

LmTinyToon 29.05.2019 14:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
127
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Bison по умолчанию генерирует парсеры LALR(1). LALR(1) означает просмотр вперед 1 токена слева направо синтаксического анализатора.

Ваши грамматики не LALR(1). На ОП не ясно, ожидать ли фу или бар. Это конфликт уменьшения/уменьшения.

Смотри сюда: https://en.wikipedia.org/wiki/LALR_parser

Но обычно Bison может генерировать парсер LR. По крайней мере, запись в вики здесь утверждает, что: https://en.wikipedia.org/wiki/GNU_Bison

Ваш случай - "загадочный конфликт": https://www.gnu.org/software/bison/manual/html_node/Mysterious-Conflicts.html#Mysterious-Conflicts

Это конфликт сдвига/уменьшения, а не конфликт уменьшения/уменьшения: уменьшите пустой foo или сдвиньте (, что вызовет foo. bar невозможно, пока foo не будет уменьшено. Таким образом, нет никаких сомнений в том, что foo требуется; вопрос в том, является ли foo пустым или нет. Так что этот случай вовсе не "загадочный".

rici 29.05.2019 22:08

Если вы хотите принять только (bar) в качестве входных данных, вы можете использовать следующее:

program_expr :   foo_expr bar_expr   {}
    |            bar_expr            {}
;    

вместо этого:

program_expr :   foo_expr bar_expr   {}
;

Тестовый вывод:

> echo "(bar)" | ./Parser 
Starting parse
Entering state 0
Reading a token: Next token is token OP ()
Shifting token OP ()
Entering state 1
Reading a token: Next token is token BAR ()
Shifting token BAR ()
Entering state 6
Reading a token: Next token is token CP ()
Shifting token CP ()
Entering state 11
Reducing stack by rule 6 (line 20):
   $1 = token OP ()
   $2 = token BAR ()
   $3 = token CP ()
-> $$ = nterm bar_expr ()
Stack now 0
Entering state 4
Reducing stack by rule 2 (line 14):
   $1 = nterm bar_expr ()
-> $$ = nterm program_expr ()
Stack now 0
Entering state 2
Reading a token: Now at end of input.
....

Но почему этот образец работает? какая разница в грамматике?

LmTinyToon 29.05.2019 14:07

Теперь он принимает bar_expr в качестве входных данных. Раньше требовалось foo_expr перед bar_expr.

SKi 29.05.2019 14:13

Обратите внимание, что это на самом деле увеличивает количество конфликтов, если вы не удалите пустую альтернативу из foo_expr.

sepp2k 29.05.2019 14:28

Другие вопросы по теме