лучший способ построить массив (или коллекцию) разнородных типов данных в семантических действиях bison/yacc

Думайте об этом как о наборе элементов, которые не обязательно все одного типа. У меня есть следующий код:

// The struct I'll use inside Bison to dynamically create collections:
typedef struct ListElementType {
    union value {
        int intVal;
        float floatVal;
        char* charptrVal;
    } value;

    struct ListElementType* next;
} ListElementType;

Тогда в Bison у меня есть:

%union
{
    int int_type;
    char char_type;
    float float_type;
    char* charptr_type;
    ListElementType* listElementType;
}
//----------------------------------------------------
%token <charptr_type> STRING
%token <int_type> INTEGER
%token <float_type> REAL
%type<listElementType> ElementList
//----------------------------------------------------
//----------------------------------------------------
ElementList
: ElementList ',' LiteralType
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = $1;
    $$->value = $3;
}

| LiteralType
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = 0;
    $$->value = $1;
}
;
//----------------------------------------------------
LiteralType
: STRING
| INTEGER
| REAL
;

Здесь есть несколько вещей/проблем. Но сначала, пытаясь создать синтаксический анализатор, подобный этому, Бизон говорит, что $3 в рекурсивном производстве и $1 в базовом/терминальном случае не имеют объявленных типов. Насколько я понимаю, у них действительно есть объявленные типы. Они имеют тип LiteralType и, как таковые, могут быть либо строками, либо целыми числами, либо числами с плавающей запятой, которые должны быть установлены автоматически, оставив пустыми последние выходные данные терминала (учитывая, что первое, что я сделал, это сделал их тип явным, выбрав соответствующий из глобального объединения) .

Во-вторых, я не ожидаю, что Bison будет жаловаться на отсутствие объявленного типа, а скорее на конфликт или двусмысленность, поскольку я присваиваю $$->value, но $2,$1 может иметь любое из трех возможных значений (в зависимости от того, какое объединение участник был назначен в их соответствующих постановках). В этой ситуации я сделал член значения в структуре ListElementType объединением. Я думал вместо того, чтобы пытаться воспользоваться тем фактом, что первый член структуры будет находиться в месте "метки" самого адреса структуры, а также что все члены объединения начинаются также с адреса памяти объединения, чтобы попытаться и напрямую назначить независимо от тип. Что-то вроде (void)$$ = $2, чем бы ни было $2.

Итак, я изменил код на:

//----------------------------------------------------
ElementList
: ElementList ',' LiteralType
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = $1;
    *$$ = (void*)$3;
}

| LiteralType
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = 0;
    $$->value = $1;
}
;
//----------------------------------------------------
LiteralType
: STRING
{
    $<charptr_type>$ = $1;
}

| INTEGER
{
    $<int_type>$ = $1;
}

| REAL
{
    $<float_type>$ = $1;
}

;

Теперь я явно установил объединение для случаев INT, REAL, STRING. Что я считал не нужным, но кто-то поправит меня, если я ошибаюсь. И я также попробовал присваивание объединения без типов, но все те же ошибки: у $3 и $1 нет объявленных типов.

Итак, мои мысли и вопросы:

Должен ли я создавать отдельные продукты StringList, IntList и RealList, где единственное, что меняется, это то, что нетерминал с правой стороны прямо соответствует определенному типу элемента в списке, например:

//----------------------------------------------------
ElementList
: IntElementList
| RealElementList
;

IntElementList
: IntElementList ',' INTEGER
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = $1;
    $$->intVal = $3;
}

| INTEGER
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = 0;
    $$->intVal = $1;
}

RealElementList
: RealElementList ',' REAL
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = $1;
    $$->floatVal = $3;
}

| REAL
{ 
    $$ = malloc(sizeof(listElementType));
    $$->next = 0;
    $$->floatVal = $1;
}

;

Или есть способ указать, что LiteralType может иметь любое из трех значений, а затем попытаться получить бестиповое объединение?

Или весь подход неверен, и есть лучший способ?


person SaldaVonSchwartz    schedule 16.10.2012    source источник


Ответы (3)


Как правило, вам нужно иметь тег типа в вашем гетерогенном типе списка:

typedef enum ListElementType { INTEGER, REAL, STRING } ListElementType
typedef struct ListElement {
    ListElementType  type;
    union {
        int intVal;
        float floatVal;
        char* charptrVal;
    } value;
    struct ListElement* next;
} ListElement;

Затем всякий раз, когда вы создаете ListElement, вы устанавливаете поле type соответствующим образом. Позже вы можете проверить поле type, чтобы узнать, что это такое.

Ваш код бизона становится таким:

%union
{
    int int_type;
    char char_type;
    float float_type;
    char* charptr_type;
    ListElement* listElement;
    struct { ListElement *head, *tail } list;
}
//----------------------------------------------------
%token <charptr_type> STRING
%token <int_type> INTEGER
%token <float_type> REAL
%type<list> ElementList
%type<listElement> LiteralType
//----------------------------------------------------
%%
//----------------------------------------------------
ElementList
: ElementList ',' LiteralType
    { $$.head = $1.head;
      $$.tail = $1.tail->next = $3; }
| LiteralType
    { $$.head = $$.tail = $1; }
;
//----------------------------------------------------
LiteralType
: STRING  { ($$ = NewListElement(STRING))->value.charptrVal = $1; }
| INTEGER { ($$ = NewListElement(INTEGER))->value.intVal = $1; }
| REAL    { ($$ = NewListElement(REAL))->value.floatVal = $1; }
;
%%
ListElement *NewListElement(ListElementType type) {
    ListElement *rv = malloc(sizeof(ListElement));
    rv->type = type;
    rv->next = 0;
    return rv; }
person Chris Dodd    schedule 16.10.2012
comment
Я согласен. Но помимо того, что вы дали ему перечисление вместо моего типа char и функции конструктора, разве это не то, что я сделал в своем ответе? - person SaldaVonSchwartz; 17.10.2012

В итоге я остановился на этом подходе.

  1. Обратите внимание, что вместо того, чтобы производство элементов (например, нетерминальный тип LiteralType) сводилось к объединению, оно сводилось к структуре с объединением и элементом типа. Член типа — это способ указать тип для каждого элемента, хранящегося в коллекции.
  2. Также обратите внимание, что структура ListType имеет указатель void* на элемент. В этом надуманном примере члена типа структуры ElementType было бы достаточно. Однако я делаю элемент общим указателем, чтобы использовать те же структуры для хранения, в свою очередь, списков объявлений, которые состоят из списков элементов.

%code требует { typedef struct Element {

%code requires {
    typedef struct Element {
        union {
            int intVal;
            float floatVal;
            char* charptrVal;            
        };

    char type;

    } ElementType;

    typedef struct ListType {
        void* element;
        struct ListType* next;

    } ListType;
}

%union
{
    int int_type;
    char char_type;
    float float_type;
    char* charptr_type;
    ListType* ListType;
    ElementType* ElementType;
}



%token <charptr_type> KEYWORD
%token <charptr_type> ID
%token <charptr_type> STRING
%token <int_type> INTEGER
%token <float_type> REAL
%token END 0


%type<ElementType> Element
%type<ListType> ElementList

//----------------------------------------------------
ElementList
: Element ',' ElementList
{
    $$ = malloc(sizeof(ListType));
    $$->element = (void*)$1;
    $$->next = $3;
}

| Element
{
    $$ = malloc(sizeof(ListType));
    $$->element = (void*)$1;
    $$->next = NULL;
}
;
//----------------------------------------------------
Element
: STRING
{
    char* aString = malloc(sizeof(char)*strlen($1)+1);
    strcpy(aString, $1);
    free(yylval.charptr_type);

    $$ = malloc(sizeof(ElementType));
    $$->charptrVal = aString;
    $$->type = 's';
}
| INTEGER
{
    $$ = malloc(sizeof(ElementType));
    $$->intVal = $1;
    $$->type = 'i';
}

| REAL
{
    $$ = malloc(sizeof(ElementType));
    $$->floatVal = $1;
    $$->type = 'f';    
}
;
person SaldaVonSchwartz    schedule 16.10.2012
comment
Никогда не играйте с yylval в парсере; вы хотели освободить $1-›charptr_type. Ваш malloc для строки при обработке STRING, конечно, должен использовать strdup, но в любом случае это совершенно бесполезно, просто передайте указатель: $$->charptrVal = $1 это все, что вам нужно. - person akim; 17.10.2012
comment
Я не знал, что его так хранят. Имеет смысл. Спасибо! - person SaldaVonSchwartz; 17.10.2012

Я думаю, вы упускаете тот факт, что Bison не пытается реализовать полную проверку типов C. Поскольку вы дали разные имена типам STRING и LiteralType, его задачей является сообщить, что его действие по умолчанию ($$ = $1) делает что-то странное с точки зрения (bison-)проверки типов. Если вы хотите использовать назначение по умолчанию, просто дайте им тот же тип (значение в вашем случае).

Кроме того, вы дважды кодируете значение объединения, что не кажется необходимым:

%code requires
{
  typedef struct ListElementType {
    union value {
      int intVal;
      float floatVal;
      char* charptrVal;
    } value;

    struct ListElementType* next;
  } ListElementType;
}

%union
 {
   union value value;
   ListElementType* list;
 };

%token <value> STRING INTEGER REAL
%type <value> LiteralType 
%type <list> ElementList
%%
ElementList
: ElementList ',' LiteralType
{ 
  $$ = malloc(sizeof($$));
  $$->next = $1;
  $$->value = $3;
}
| LiteralType
{ 
  $$ = malloc(sizeof($$));
  $$->next = 0;
  $$->value = $1;
}
;
//----------------------------------------------------
LiteralType
: STRING
| INTEGER
| REAL
;
person akim    schedule 16.10.2012
comment
Я понимаю вашу точку зрения, но если я возьму ваш пример, Bison по-прежнему дает конфликты типов для назначений по умолчанию для LiteralType: - person SaldaVonSchwartz; 16.10.2012
comment
Кроме того, даже если я добавлю себе $$.intVal = $1;,etc к каждому действию для литералов, то bison больше не жалуется, но теперь gcc жалуется, что ListelementType является неизвестным типом - person SaldaVonSchwartz; 16.10.2012
comment
Нет, он не сообщает о столкновениях. Вероятно, вы пропустили ту часть, где я указываю %type <value> LiteralType, а также оставили все измененные %token <value>. Так что, действительно, с чем-то другим, чем я ответил, у вас может быть другой результат :) - person akim; 16.10.2012
comment
1- моя версия (2.6) по-прежнему требует, чтобы я явно выполнял задания в постановках LiteralType. Без них просто не будет и сообщать о стычках. 2 - Добавление этого фактически устранило проблему. 3 - неизвестный тип ListElementType был связан с bison 2.3 (когда я перешел на 2.6, это исчезло). 4 - я не вижу, где я дважды кодирую объединение. 5. Почему вы объявляете, что STRING, REAL и INTEGER имеют тот же тип, что и LiteralType, а это не так. Эти 3 — токены/терминалы. Они не являются нетерминалами и имеют определенные типы. - person SaldaVonSchwartz; 16.10.2012
comment
6 - единственная проблема, которую я вижу в вашем подходе, заключается в том, что впоследствии невозможно определить, какой тип элемента у вас есть в коллекции, поскольку коллекция может быть разнородной. - person SaldaVonSchwartz; 16.10.2012
comment
Привет Салда. 1 - Нет уж, не жалуется. Я скачал и установил 2.6, и файл, который я предоставил, компилируется без проблем. 4 - Вы дважды указываете членов своего союза: один раз в определении значения союза, а затем в %union. Я предложил способ избежать этого дублирования. 5 - Ваш код решил использовать один тип объединения в вашем типе списка, поэтому я просто продолжил в том же духе: у них действительно один и тот же тип: значение объединения. 6 - проблема не в подходе, а в вашем типе списка, который должен использовать какое-то перечисление, например, для записи того, что он хранит. - person akim; 17.10.2012