cara terbaik untuk membangun array (atau kumpulan) tipe data heterogen dalam tindakan semantik bison/yacc

Anggap saja ini lebih merupakan kumpulan elemen yang belum tentu semuanya memiliki tipe yang sama. Saya memiliki kode berikut:

// 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;

Lalu di Bison saya punya:

%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
;

Ada beberapa hal/masalah di sini. Namun pertama-tama, mencoba membuat parser seperti ini Bison mengatakan bahwa $3 dalam produksi rekursif dan $1 dalam kasus dasar /terminal tidak memiliki tipe yang dideklarasikan. Menurut saya, mereka sebenarnya memiliki tipe yang dideklarasikan. Mereka adalah LiteralType dan dengan demikian, dapat berupa string atau int atau float, yang harus diatur secara otomatis dengan membiarkan produksi terminal terakhir kosong (mengingat hal pertama yang saya lakukan adalah membuat tipenya eksplisit dengan memilih yang sesuai dari gabungan global) .

Kedua, saya tidak berharap Bison mengeluh bahwa tidak ada tipe yang dideklarasikan melainkan ada bentrokan atau ambiguitas karena saya menugaskannya ke $$->value tetapi $2,$1 dapat memiliki salah satu dari tiga nilai yang mungkin (tergantung pada gabungan mana anggota ditugaskan dalam produksi masing-masing). Untuk situasi ini saya membuat anggota nilai dalam struct ListElementType menjadi gabungan. Saya berpikir alih-alih mencoba mengambil keuntungan dari fakta bahwa anggota pertama suatu struct akan berada di lokasi "label" dari alamat struct itu sendiri ditambah bahwa semua anggota serikat pekerja juga memulai pada alamat mem serikat untuk mencoba dan langsung menetapkan terlepas dari jenis. Sesuatu seperti (void)$$ = $2, apa pun $2 yang terjadi.

JADI, saya mengubah kode menjadi:

//----------------------------------------------------
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;
}

;

Sekarang saya telah secara eksplisit mengatur gabungan untuk kasus INT, REAL,STRING. Yang saya pikir tidak perlu, tetapi seseorang mengoreksi saya jika saya salah. DAN, saya juga mencoba penugasan gabungan tanpa tipe tetapi kesalahannya masih sama: $3 dan $1 tidak memiliki tipe yang dideklarasikan.

Jadi pendapat saya, pertanyaan:

Haruskah saya membuat produksi StringList, IntList, dan RealList terpisah di mana satu-satunya hal yang berubah adalah nonterminal sisi kanan langsung merupakan tipe elemen tertentu dalam daftar, seperti:

//----------------------------------------------------
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;
}

;

Atau adakah cara untuk menyatakan bahwa LiteralType dapat memiliki salah satu dari tiga nilai dan kemudian mencoba dan menarik tugas gabungan tanpa tipe?

Atau apakah keseluruhan pendekatannya salah dan ada cara yang lebih baik?


person SaldaVonSchwartz    schedule 16.10.2012    source sumber


Jawaban (3)


Umumnya yang ingin Anda lakukan adalah memiliki tag type di tipe daftar heterogen Anda:

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

Kemudian setiap kali Anda membuat ListElement, Anda menyetel bidang type dengan tepat. Nanti, Anda dapat memeriksa kolom type untuk melihat apa itu.

Kode bison Anda kemudian menjadi:

%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
Saya setuju. Tapi selain Anda memberinya enum alih-alih tipe char dan fungsi konstruktor saya, bukankah ini pada dasarnya yang saya lakukan dalam jawaban saya? - person SaldaVonSchwartz; 17.10.2012

Saya akhirnya memilih pendekatan ini.

  1. Perhatikan bahwa alih-alih membuat produksi Elemen (misal nonterminal LiteralType) direduksi menjadi gabungan, ia direduksi menjadi struct dengan gabungan dan anggota tipe. Anggota tipe adalah cara untuk memberi tahu tipe setiap elemen yang disimpan dalam koleksi.
  2. Perhatikan juga bahwa struct ListType memiliki penunjuk void* ke elemen. Dalam contoh yang dibuat ini, anggota tipe struct ElementType sudah cukup. Namun, saya membuat elemen tersebut sebagai penunjuk umum untuk menggunakan struct yang sama untuk menyimpan, pada gilirannya, daftar deklarasi yang terdiri dari daftar elemen.

%kode memerlukan { 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
Jangan pernah bermain dengan yylval di parser; Anda ingin membebaskan $1-›charptr_type. Malloc Anda untuk string dalam penanganan STRING tentu saja harus menggunakan strdup, tetapi itu sama sekali tidak berguna, cukup berikan pointer: $$->charptrVal = $1 saja yang Anda butuhkan. - person akim; 17.10.2012
comment
Saya tidak tahu itu disimpan seperti itu. Masuk akal. Terima kasih! - person SaldaVonSchwartz; 17.10.2012

Saya pikir Anda melewatkan fakta bahwa Bison tidak mencoba menerapkan pemeriksaan tipe C penuh. Karena Anda telah memberikan nama tipe yang berbeda ke STRING dan LiteralType, tugasnya adalah melaporkan bahwa tindakan defaultnya ($$ = $1) melakukan sesuatu yang aneh dari sudut pandang pemeriksaan tipe (bison-). Jika Anda ingin menggunakan tugas default, berikan saja jenis yang sama (nilai dalam kasus Anda).

Selain itu, Anda mengkodekan dua kali nilai gabungan, itu sepertinya tidak perlu:

%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
Saya mengerti maksud Anda, tetapi jika saya menggunakan contoh Anda, Bison masih memberikan typeclash untuk penetapan default LiteralType: ‹value› != ‹charptr_type› ‹value› != ‹int_type› ‹value› != ‹float_type› - person SaldaVonSchwartz; 16.10.2012
comment
Selain itu, meskipun saya menambahkan diri saya sendiri $$.intVal = $1;,etc ke setiap tindakan untuk literal, maka bison tidak mengeluh lagi tetapi sekarang gcc mengeluh bahwa ListelementType adalah tipe yang tidak diketahui - person SaldaVonSchwartz; 16.10.2012
comment
Tidak, ini tidak melaporkan bentrokan. Anda mungkin telah meninggalkan bagian di mana saya menentukan %type <value> LiteralType, dan Anda juga meninggalkan semua %token <value> yang diubah. Jadi, memang, dengan jawaban lain selain yang saya jawab, Anda mungkin mendapatkan hasil yang berbeda :) - person akim; 16.10.2012
comment
1- versi saya (2.6) masih mengharuskan saya melakukan tugas secara eksplisit dalam produksi LiteralType. Mereka tidak akan bisa melakukannya tanpa mereka dan melaporkan bentrokan tersebut. 2 - Menambahkan ini sebenarnya memperbaiki masalah. 3 - tipe ListElementType yang tidak diketahui disebabkan oleh bison 2.3 (ketika saya beralih ke 2.6, itu hilang.) 4 - Saya tidak melihat di mana saya mengkodekan gabungan dua kali. 5 - Mengapa Anda mendeklarasikan STRING, REAL, dan INTEGER memiliki tipe yang sama dengan LiteralType, padahal sebenarnya tidak. Ketiganya adalah token/terminal. Mereka bukan nonterminal dan memiliki tipe tertentu. - person SaldaVonSchwartz; 16.10.2012
comment
6 - satu masalah yang saya lihat dengan pendekatan Anda adalah tidak ada cara untuk mengetahui jenis elemen apa yang Anda miliki dalam koleksi, karena koleksinya bisa heterogen. - person SaldaVonSchwartz; 16.10.2012
comment
Hai Salda. 1 - Tidak juga, ia tidak mengeluh. Saya telah mengunduh dan menginstal 2.6, dan file yang saya berikan dikompilasi dengan bersih. 4 - Anda menentukan dua kali anggota serikat Anda: sekali dalam definisi nilai serikat pekerja, dan kemudian di %union. Saya menyarankan cara untuk menghindari duplikasi ini. 5 - Kode Anda memilih untuk menggunakan satu tipe gabungan, dalam tipe daftar Anda, jadi saya melanjutkan seperti ini: keduanya memiliki tipe yang sama: nilai gabungan. 6 - masalahnya bukan pada pendekatannya, tetapi pada tipe daftar Anda yang harus menggunakan beberapa enum misalnya untuk mencatat apa yang disimpannya. - person akim; 17.10.2012