Проблемы с использованием рекурсивных правил Bison и сохранением значений с их использованием

#grammar #bison #context-free-grammar

#грамматика #bison #контекстно-свободная грамматика

Вопрос:

Я пытаюсь создать сканер и анализатор flex bison для деревьев формата файлов Newick, чтобы выполнять с ними операции. Реализованная грамматика объяснение основано на упрощении (метки и длины всегда одного и того же типа, возвращаемые flex) этого примера.

По сути, это анализатор для формата файла, который представляет собой дерево с серией (рекурсивных) поддеревьев и / или листьев. Основное дерево всегда будет заканчиваться на; и указанное дерево и все поддеревья внутри будут содержать ряд узлов между ( и ), с именем и длиной справа от самой правой круглой скобки, заданными name и :length , которые являются необязательными (вы можете не указывать их, поместите один из них(имя или: длина), или оба с именем: длина).

Если в каком-либо узле отсутствует имя или длина, будут применены значения по умолчанию. (например: ‘missingName’ и ‘1’)

Примером может быть (child1:4, child2:6)root:6; , ((child1Of1:2, child2Of1:9)child1:5, child2:6)root:6;

Реализация указанной грамматики следующая (ПРИМЕЧАНИЕ: я перевел свой собственный код, как это было на моем языке, и для ясности было удалено много побочных вещей):

     struct node {
        char* name; /*the node's assigned name, either from the file or from default values*/
        float length; /*node's length*/
    } dataOfNode;
}

 

%start tree
 
%token<dataOfNode> OP CP COMMA SEMICOLON COLON DISTANCE NAME

%type<dataOfNode> tree subtrees recursive_subtrees subtree leaf


%%

tree:   subtrees NAME COLON DISTANCE SEMICOLON          {} // with name and distance    
        | subtrees NAME SEMICOLON                       {} // without distance
        | subtrees COLON DISTANCE SEMICOLON             {} // without name
        | subtrees SEMICOLON                            {} // without name nor distance
        ;
    
    
subtrees:   OP recursive_subtrees CP                    {}
            ;   
        
            
recursive_subtrees: subtree                             {} // just one subtree, or the last one of the list         
                    | recursive_subtrees COMMA subtree  {} // (subtree, subtree, subtree...)        
    

subtree:    subtrees NAME COLON DISTANCE    {   $.NAME= $2.name; $.length = $4.length; $.lengthAcum = $.lengthAcum   $4.length;
                                                } // group of subtrees, same as the main tree but without ";" at the end, with name and distance                                        
            
            | subtrees NAME                 {   $.name= $2.name; $.length = 1.0;}             // without distance                                 
            
            | subtrees COLON DISTANCE       {   $.name= "missingName"; $.length = $3.length;} // without name                             
           
            | subtrees                      {   $.name= "missingName"; $.length = 1.0;}       // without name nor distance                            
            
            | leaf                          {   $.name= $1.name; $.length = $1.length;}       // a leaf
                    
                    
                    
leaf:   NAME COLON DISTANCE {   $.name= $.name; $.length = $3.length;}       // with name and distance
        | NAME              {   $.name= $1.name; $.length = 1.0;}             // without distance
        | COLON DISTANCE    {   $.name= "missingName"; $.length = $2.length;} // without name
        |                   {   $.name= "missingName"; $.length = 1.0;}       // without name nor distance
        ;


%%
 

Теперь, допустим, я хочу различать, кто является родителем каждого поддерева и листа, чтобы я мог рекурсивно накапливать длину родительского поддерева с длиной «самого длинного» дочернего элемента.

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

Комментарии:

1. Здесь есть вопрос? Вы разбираете каждое leaf из них на name length , но затем вы ничего не делаете с ним в правилах subtrees или recursive_subtrees, поэтому они просто отбрасываются.

Ответ №1:

В основном это вопрос определения структуры данных, в которой вы хотите хранить свои деревья, и построения этого «снизу вверх» в действиях правил. Часть «снизу вверх» является важным следствием того, как работают анализаторы bison — они «снизу вверх», распознают конструкции из листьев грамматики и затем собирают их в более высокие нетерминалы (и, безусловно, в начальный нетерминал, который будет последним выполнением действия). Вы также можете упростить ситуацию, не имея так много избыточных правил. Наконец, IMO всегда лучше использовать символьные литералы для односимвольных токенов, а не имен. Таким образом, вы можете получить:

 %{
struct tree {
    struct tree  *next;     /* each tree is potentially part of a list of (sub)trees */
    struct tree  *subtree;  /* and may contain a subtress */
    const char   *name;
    double       length;
};

struct tree *new_leaf(const char *name, double length);   /* malloc a new leaf "tree" */
void append_tree(struct tree **list, struct tree *t);  /* append a tree on to a list of trees */
%}

%union {
    const char   *name;
    double       value;
    struct tree  *tree;
}

%type<tree> subtrees recursive_subtrees subtree leaf
%token<name> NAME
%token<value> DISTANCE

%%

tree: subtrees leaf ';'  { $2->subtree = $1;  print_tree($2); } 
    ;
    
subtrees: '(' recursive_subtrees ')'  { $ = $2; }
        ;   
            
recursive_subtrees: subtree                         { $ = $1; } // just one subtree, or the last one of the list
                  | recursive_subtrees ',' subtree  { append_tree(amp;($=$1)->next, $3); } // (subtree, subtree, subtree...)
                  ;

subtree: subtrees leaf  { ($=$2)->subtree = $1; }
       | leaf           { $ = $1; }
       ;
                    
leaf: NAME ':' DISTANCE { $ = new_leaf($1, $3);}              // with name and distance
    | NAME              { $ = new_leaf($1, 1.0);}             // without distance
    | ':' DISTANCE      { $ = new_leaf("missingName", $2; }   // without name
    |                   { $ = new_leaf("missingName", 1.0); } // without name nor distance
    ;

%%

struct tree *new_leaf(const char *name, double length) {
    struct tree *rv = malloc(sizeof(struct tree));
    rv->subtree = rv->next = NULL;
    rv->name = name;
    rv->length = length;
    return rv;
}

void append_tree(struct tree **list, struct tree *t) {
    assert(t->next == NULL);  // must not be part of a list yet
    while (*list) list = amp;(*list)->next;
    *list = t;
}
 

Комментарии:

1. Вау, большое вам спасибо. Это довольно сложно проанализировать, но это действительно облегчило понимание!