不具备语义价值是否是一个很好的野牛实践,但使用行动的副作用?

时间:2016-11-16 10:06:59

标签: parsing bison

我有一种语言,其中包含所有内容的语义,是一个字符数组或数组数组。所以我有以下YYSTYPE:

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)是:

%token  WORD
%%
array       :       WORD    
            | array WORD    
            ;

所以我接受一系列的话。对于每个单词,语义值变为该字符数组,然后我想将这些中的每一个附加到数组数组中,用于整个序列。

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

  1. array符号的语义值为array。如果我执行此操作,则array WORD的操作必须将数组$1复制到$$这很慢,所以我不喜欢这样。

    < / LI>
  2. array符号的语义值为array *。现在array WORD的操作,我只需添加到数组*$1,然后将$$设置为等于$1。但是我不喜欢这个有两个原因。首先,语义含义不是array的指针,而是array。其次,对于规则array : WORD的操作,我必须malloc结构,这是缓慢的。是的,&#39;追加&#39;有时会做malloc,但如果我分配的不够频繁。出于性能原因,我希望避免任何不必要的malloc

  3. 忘记尝试为符号array设置语义值,并使用全局变量:

    static YYSTYPE g_array;

    YYSTYPE *g_parray = &g_array;

  4. 然后,操作将只使用

    append(g_parray, word_array)
    

    整个语法的工作方式,我不需要多个g_array。以上是我能想到的最快的。但这是非常糟糕的设计 - 很多全局变量,没有语义值,相反,一切都是由全局变量的副作用发生的。

    所以,我个人并不喜欢他们中的任何一个。这是野牛普遍接受的最佳做法?

1 个答案:

答案 0 :(得分:1)

在大多数情况下,使用全局变量没有意义。或多或少的现代版本的野牛有%parse-param指令,它允许你有一种解析上下文&#39;。上下文可以处理所有内存分配等。

它可能反映当前的解析状态 - i。即有“当前array&#39;的概念在这种情况下,您的语义操作可以依赖于知道您所在位置的上下文。

%{
    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); }
    ;

如果您解析单词列表而不解析任何其他单词,则可以 it 您的上下文。

然而,在一个简单的语法中,如果你通过YYSTYPE传递信息会更清楚:

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