Я только начал работать с Bison и столкнулся с несколькими проблемами. Цель моей грамматики - распознать язык «конвейера команд», в котором выходные данные одной команды могут передаваться по конвейеру как входные данные другой команды. Каждая команда может также иметь список параметров с необязательным значением. Вот пример:

command1 --param1 test --param2 test2 | command2 | command3 --param3

В предыдущем примере command1 принимает два параметра: param1 со значением test и param2 со значением test2. Выходные данные command1 затем передаются по конвейеру в command2, которая не принимает никаких параметров. Наконец, результат command2 передается по конвейеру в command3, который принимает один параметр (параметры без значения считаются переключателем).

Соответствующая грамматика зубра приведена ниже:

%{

#include 
#include 
#include "AST.h"
#include "Evaluator.h"

void yyerror (const char *error);
int  yylex();

%}

%code{
    Evaluator eval;
}

%union {
  char* s;
  double d;
  AbstractNode* abstractnode;
  CmdletCall* cmdletdef;
  ParameterDef* parameterdef;
  ParameterListDef* paramlistdef;
}

/* declare tokens */

%token EOL
%token PARAMETERNAME
%token BUILTIN
%token IDENTIFIER


%type  parameterDef
%type  cmdletCall
%type  pipeLineDef    
%type  paramList
%%

input: /* nothing */
    | input pipeLineDef EOL {
                                $2->accept(eval);
                                delete $2;
                                std::cout << eval.result() << std::endl;
                            }                        
    | input EOL             {}
    ;



pipeLineDef: cmdletCall                  {$$ = $1;}      
    | pipeLineDef '|' cmdletCall         {$$ = new PipeLineDef($1, $3);}   
    ;


cmdletCall: IDENTIFIER                   {$$ = new CmdletCall($1);}                 
          | cmdletCall paramList         {$1->setParameterList($2);}   
          ;

paramList: parameterDef                 {  
                                            $$ = new ParameterListDef;
                                            $$->addChildNode($1);
                                        }  
         | paramList parameterDef       {
                                            $1->addChildNode($2);
                                            $$ = $1;
                                        }
         ;

parameterDef: PARAMETERNAME             {$$ = new ParameterDef($1);}
            | parameterDef IDENTIFIER   {
                                            $1->setValue($2);
                                        }         

            ;




%%


void yyerror (const char *error)
{
  std::cout << error << std::endl;
}

В предыдущей грамматике есть один конфликт сдвига-уменьшения, который воспроизводится ниже:

Terminals unused in grammar

BUILTIN

State 10 conflicts: 1 shift/reduce
State 10

7 cmdletCall: cmdletCall paramList .
9 paramList: paramList . parameterDef

PARAMETERNAME  shift, and go to state 9

PARAMETERNAME  [reduce using rule 7 (cmdletCall)]
$default       reduce using rule 7 (cmdletCall)

parameterDef  go to state 13

Я хотел бы устранить конфликт, но я не уверен, как мне следует действовать (я понимаю, что конфликт сдвига / уменьшения указывает на неоднозначную грамматику). Однако, судя по моим первоначальным тестам, все работает так, как ожидалось. Еще один момент, который меня озадачивает, - это то, как Bison оценивает терминалы. Например, если я перепишу правила cmdletCall и paramList следующим образом, грамматика нарушится:

cmdletCall: IDENTIFIER paramList         {$$ = new CmdletCall($1);    $$->setParameterList($2);}   
          ;

paramList: /*nothing*/ 
                                        {  
                                            $$ = new ParameterListDef;

                                        }  
         | paramList parameterDef       {
                                            $1->addChildNode($2);
                                            $$ = $1;
                                        }
         ;

Если я перепишу грамматику, как показано выше, то для ввода, например:

command1 --param1

В правиле cmdletCall значение $ 1, которое соответствует токену IDENTIFIER, будет command1 --param1, вместо только command1.

BigONotation

Ответов: 1

Ответы (1)

Ваше определение cmdletCall фактически равно IDENTIFIER paramList * (с использованием стандартного звездообразного оператора Клини на обычном языке). Но paramList это paramDef +. Так что это явно двусмысленно; невозможно определить, сколько paramLists следует за IDENTIFIER, потому что нет указания, где заканчивается один и начинается следующий.

Фактически, вы хотите иметь только (или максимум) один paramList. Есть несколько вариантов, но самый простой:

cmdletCall: IDENTIFIER paramList
paramList : /* empty */
          | paramList parameterDef

Другой вариант - оставить paramList не допускающим значения NULL и добавить параметр cmdletCall, который состоит только из IDENTIFIER. На самом деле, это полезно только в том случае, если вам нужно разделить регистры для семантического правила, но, безусловно, возможно.

Или для более простой грамматики просто избавьтесь от paramList:

cmdletCall: IDENTIFIER
          | cmdletCall parameterDef

Поскольку синтаксические анализаторы, сгенерированные bison, предпочитают shift, чтобы уменьшить, синтаксический анализатор, созданный на основе вашей неоднозначной грамматики, будет делать то, что вы ожидаете: генерировать только ноль или один paramList для каждой команды. Но вам все равно рекомендуется устранить двусмысленность.


The problem you had with the associated semantic value is the result of not copying the contents of yytext from your scanner; yytext is an internal datastructure in the flex-built scanner, and its contents do not belong to you, since the scanner can and will modify them as it sees fit.

2022 WebDevInsider