Parsing 野牛不使用语义值,而是使用行为的副作用,这是一种好的做法吗?

Parsing 野牛不使用语义值,而是使用行为的副作用,这是一种好的做法吗?,parsing,bison,Parsing,Bison,我有一种语言,其中所有事物的语义都是一组字符或数组。所以我有以下几点: typedef struct _array { union { char *chars; // start of string void *base; // start of array }; unsigned n; // number of valid elements in above unsigned allocated; // number

我有一种语言,其中所有事物的语义都是一组字符或数组。所以我有以下几点:

typedef struct _array {
    union {
        char *chars; // start of string
        void *base;  // start of array
    };
    unsigned n;      // number of valid elements in above
    unsigned allocated;  // number of allocated elements for above
} array;

#define YYSTYPE array
我可以用

void append(YYSTYPE *parray, YYSTYPE *string);
假设语法(SSCCE)为:

所以我接受一系列的单词。对于每个单词,语义值都变成了字符数组,然后我想将每个字符都附加到整个序列的数组中

有几种可能的方法来设计动作:

  • Have
    array
    符号具有类型
    array
    的语义值。如果我这样做,那么
    数组单词
    的操作将不得不将数组
    $1
    复制到
    $$
    ,这很慢,所以我不喜欢这样

  • Have
    array
    符号具有类型
    array*
    的语义值。现在,对于
    数组单词
    ,我只需将
    *$1
    添加到数组中,然后将
    $$
    设置为等于
    $1
    。但我不喜欢这个有两个原因。首先,语义不是指向
    数组
    ,而是
    数组
    。第二,对于规则
    array:WORD
    的操作,我必须
    malloc
    这个结构,它很慢。是的,“append”有时会执行
    malloc
    ,但是如果我分配的足够多,就不会频繁。出于性能原因,我希望避免任何不必要的
    malloc

  • 忘记尝试为符号
    数组
    设置语义值,使用globals:

    静态YYSTYPE g_阵列

    YYSTYPE*g_parray=&g_数组

  • 然后,这些操作将只使用

    append(g_parray, word_array)
    
    按照整个语法的工作方式,我只需要一个
    g_数组
    。以上是我能想到的最快的。但这真的是一个糟糕的设计——有很多全局变量,没有语义值,相反,一切都是全局变量的副作用


    所以,就我个人而言,我一个都不喜欢。对于野牛来说,哪一个是公认的最佳实践

    在大多数情况下,使用全局变量没有意义。bison的现代版本或多或少都有
    %parse param
    指令,它允许您拥有某种“解析上下文”。上下文可能负责所有内存分配等

    它可能反映了当前的解析状态-i。E有“当前<代码>数组等”的概念。在这种情况下,您的语义操作可以依赖于上下文知道您在哪里

    %{
        typedef struct tagContext Context;
        typedef struct tagCharString CharString;
    
        void start_words(Context* ctx);
        void add_word(Context* ctx, CharString* word);
    %}
    
    %union {
        CharString* word;
    }
    %parse-param {Context* ctx}
    
    %token<word> WORD
    %start words
    
    %%
    
    words
        : { start_words(ctx); } word
        | words                 word
        ;
    
    word
        : WORD { add_word(ctx, $1); }
        ;
    
    这两种方法之间的性能差异似乎可以忽略不计

    内存清理

    如果您的输入文本在解析时没有错误,则您始终负责清理所有动态内存。但是,如果输入文本导致分析错误,解析器将不得不丢弃一些标记。在这种情况下,可能有两种清理方法

    首先,您可以跟踪上下文中的所有内存分配,并在销毁上下文时释放它们

    其次,您可以依赖bison析构函数:

    %{
        void free_word_list(WordList* word_list);
    %}
    
    %destructor { free_word_list($$); } <word_list>
    
    %{
    无效自由词列表(词列表*词列表);
    %}
    %析构函数{free_word_list($$);}
    
    您的第一种方法与我的第三种方法类似,只是没有全局方法,我将以此作为我的答案,谢谢。你的第二种方法与我的第二种方法完全相同。
    %{
        typedef struct tagContext Context;
        typedef struct tagCharString CharString;
        typedef struct tagWordList WordList;
    
        // word_list = NULL to start a new list
        WordList* add_word(Context* ctx, WordList* prefix, CharString* word);
    %}
    
    %union {
        CharString* word;
        WordList* word_list;
    }
    %parse-param {Context* ctx}
    
    %token<word> WORD
    %type<word_list> words words_opt
    %start words
    
    %%
    
    words
        : words_opt WORD { $words = add_word(ctx, $words_opt, $WORD); }
        ;
    
    words_opt
        : %empty { $words_opt = NULL; }
        | words
        ;
    
    %{
        void free_word_list(WordList* word_list);
    %}
    
    %destructor { free_word_list($$); } <word_list>