我有一种语言,其中包含所有内容的语义,是一个字符数组或数组数组。所以我有以下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
;
所以我接受一系列的话。对于每个单词,语义值变为该字符数组,然后我想将这些中的每一个附加到数组数组中,用于整个序列。
有几种可能的方法来设计行动:
array
符号的语义值为array
。如果我执行此操作,则array WORD
的操作必须将数组$1
复制到$$
这很慢,所以我不喜欢这样。
array
符号的语义值为array *
。现在array WORD
的操作,我只需添加到数组*$1
,然后将$$
设置为等于$1
。但是我不喜欢这个有两个原因。首先,语义含义不是array
的指针,而是array
。其次,对于规则array : WORD
的操作,我必须malloc
结构,这是缓慢的。是的,'追加'有时会做malloc
,但如果我分配的不够频繁。出于性能原因,我希望避免任何不必要的malloc
。
忘记尝试为符号array
设置语义值,并使用全局变量:
static YYSTYPE g_array;
YYSTYPE *g_parray = &g_array;
然后,操作将只使用
append(g_parray, word_array)
整个语法的工作方式,我不需要多个g_array
。以上是我能想到的最快的。但这是非常糟糕的设计 - 很多全局变量,没有语义值,相反,一切都是由全局变量的副作用发生的。
所以,我个人并不喜欢他们中的任何一个。这是野牛普遍接受的最佳做法?
答案 0 :(得分:1)
在大多数情况下,使用全局变量没有意义。或多或少的现代版本的野牛有%parse-param
指令,它允许你有一种解析上下文'。上下文可以处理所有内存分配等。
它可能反映当前的解析状态 - i。即有“当前array
'的概念在这种情况下,您的语义操作可以依赖于知道您所在位置的上下文。
%{
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>