没有语义值但使用动作的副作用是一种很好的野牛做法吗?
Is it a good bison practice to not have semantic values, but use side effects of actions?
我有一种语言,其中所有事物的语义都是字符数组或数组数组。所以我有以下 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
的操作将不得不将数组 </code> 复制到 <code>$$
,这很慢,所以我不喜欢那样。
有array
个符号具有array *
类型的语义值。现在 array WORD
的动作,我可以只添加到数组 *
,然后设置 $$
等于 </code>。但我不喜欢这个有两个原因。首先,语义不是指向<code>array
的指针,而是array
。其次,对于规则 array : WORD
的操作,我将不得不 malloc
结构,这很慢。是的,'append' 有时会做 malloc
,但如果我分配足够多,就不会频繁。出于性能原因,我想避免任何不必要的 malloc
。
完全忘记尝试为符号 array
赋予语义值,并使用全局变量:
static YYSTYPE g_array;
YYSTYPE *g_parray = &g_array;
然后,操作将只使用
append(g_parray, word_array)
整个语法的工作方式,我不需要超过一个 g_array
。以上是我能想到的最快的。但这确实是一个糟糕的设计——很多全局变量,没有语义值,相反,一切都是全局变量的副作用。
所以,就我个人而言,我不喜欢他们中的任何一个。哪个是普遍接受的野牛最佳实践?
在大多数情况下,使用全局变量没有意义。或多或少的现代版本的野牛有 %parse-param
指令,它允许你有一种 'parsing context'。上下文可能会处理所有内存分配等。
它可能反映了当前的解析状态-i。 e.有 'current 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, ); }
;
如果您仅解析单词列表而不解析其他任何内容,您可以将它作为您的上下文。
但是,在简单的语法中,如果通过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
;
这两种方法之间的性能差异似乎可以忽略不计。
内存清理
如果您的输入文本被正确解析,则您始终有责任清理所有动态内存。但是,如果您的输入文本导致解析错误,解析器将不得不丢弃一些标记。在这种情况下可能有两种清理方法。
首先,您可以跟踪上下文中的所有内存分配,并在销毁上下文时将它们全部释放。
其次,你可以依赖bison的析构函数:
%{
void free_word_list(WordList* word_list);
%}
%destructor { free_word_list($$); } <word_list>
我有一种语言,其中所有事物的语义都是字符数组或数组数组。所以我有以下 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
的操作将不得不将数组</code> 复制到 <code>$$
,这很慢,所以我不喜欢那样。有
array
个符号具有array *
类型的语义值。现在array WORD
的动作,我可以只添加到数组*
,然后设置$$
等于</code>。但我不喜欢这个有两个原因。首先,语义不是指向<code>array
的指针,而是array
。其次,对于规则array : WORD
的操作,我将不得不malloc
结构,这很慢。是的,'append' 有时会做malloc
,但如果我分配足够多,就不会频繁。出于性能原因,我想避免任何不必要的malloc
。完全忘记尝试为符号
array
赋予语义值,并使用全局变量:static YYSTYPE g_array;
YYSTYPE *g_parray = &g_array;
然后,操作将只使用
append(g_parray, word_array)
整个语法的工作方式,我不需要超过一个 g_array
。以上是我能想到的最快的。但这确实是一个糟糕的设计——很多全局变量,没有语义值,相反,一切都是全局变量的副作用。
所以,就我个人而言,我不喜欢他们中的任何一个。哪个是普遍接受的野牛最佳实践?
在大多数情况下,使用全局变量没有意义。或多或少的现代版本的野牛有 %parse-param
指令,它允许你有一种 'parsing context'。上下文可能会处理所有内存分配等。
它可能反映了当前的解析状态-i。 e.有 'current 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, ); }
;
如果您仅解析单词列表而不解析其他任何内容,您可以将它作为您的上下文。
但是,在简单的语法中,如果通过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
;
这两种方法之间的性能差异似乎可以忽略不计。
内存清理
如果您的输入文本被正确解析,则您始终有责任清理所有动态内存。但是,如果您的输入文本导致解析错误,解析器将不得不丢弃一些标记。在这种情况下可能有两种清理方法。
首先,您可以跟踪上下文中的所有内存分配,并在销毁上下文时将它们全部释放。
其次,你可以依赖bison的析构函数:
%{
void free_word_list(WordList* word_list);
%}
%destructor { free_word_list($$); } <word_list>