我的Bison文件中的每个非终结符——文档、消息、消息——返回一个字符串:
%union
{
char *strval;
}
%type <strval> document messages message
我的输入文件包含零条或多条消息,每个消息后跟一个换行符(EOL):
messages: message EOL messages { $$ = strcat($1, $3); }
| %empty { $$ = ???; }
;
我不知道对%empty部分使用什么操作。我尝试了很多方法。我尝试返回空字符串:
{ $$ = ''; }
导致这个错误消息:
message.y: In function 'yyparse':
message.y:25:56: error: empty character constant
25 | | %empty { $$ = ''; }
我尝试返回一个空格字符:
{ $$ = ' '; }
导致这个错误消息:
message.y: In function 'yyparse':
message.y:25:54: warning: assignment to 'char *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
25 | | %empty { $$ = ''; }
我试着转换成一个字符:
{ $$ = char(' '); }
导致这个错误消息:
message.y: In function 'yyparse':
message.y:26:56: error: expected expression before 'char'
25 | | %empty { $$ = ''; }
唷!我没有主意了。对于在返回字符串的规则中包含%empty
的替代方案,正确的操作是什么?
可以返回空字符串(""
)或NULL
。
我推荐NULL
,因为我不喜欢将字符串字面值归因于char*
类型,因为它们倾向于在常见的编译策略中指向只读内存(即在运行时重新定位到映射到只读页面的部分中)。所以理想的类型是const char*
,但这与你的代码的其余部分是不一致的。另一种策略是堆分配一个空字符串(strdup("")
),但这感觉很浪费(并且需要随后被释放,即使它是一个中介,其内容将被连接操作复制)。所以我将使用NULL
和一些逻辑来测试它。
您经常会看到解析器操作代码显式地将令牌的基础值复制到堆中。
您的代码看起来很可疑,因为它使用strcat
将结果写入第一个参数的末尾(目的地)并返回第一个参数。因此,您需要知道参数足够大(通过分配一个足够大的新堆分配来存储这两个字符串,然后复制第一个字符串的内容,然后将第二个字符串strcat
'移到末尾)。
这种模式在LR解析中经常出现。考虑下面的语法,它解析一个可能为空的整数列表:
L -> ε.
L -> int L.
在C语言中,如果使用链表,通常将空列表标识为NULL
。所以动作变成(伪代码):
L -> ε { $$ = NULL; }
L -> int L {
struct list* l = malloc(sizeof(struct list));
l->value = $1; // store value
l->next = $2; // store the tail of the list
$$ = l;
}
列表数据结构是向后构建的(因为LR执行自下而上的约简)。不同之处在于,你的代码并没有将之前缩减的结果(L -> int L
产品右侧的非终端L
)间接地存储在它正在构建的东西中,而是将它连接起来。
我建议你做下面的事情(为message
引入一个假设的定义):
// copies string contents to heap
message: IDENT { $$ = strdup($1); }
messages: message EOL messages {
if ($3 == NULL) {
// no concatenation required
$$ = $1;
} else {
// allocate space for both
char* both = realloc($1, strlen($1) + strlen($3) + 1);
$$ = strcat(both, $3);
free($3);
}
}
| %empty {
$$ = NULL;
}
;
这应该按照您期望的方式运行。它为连接的字符串创建一个新的分配,将已经减少的连接($3
)复制到新分配的末尾(以$1
为前缀),然后释放已经减少的连接($3
)。
以下是简化步骤的概念化:
foo EOL (bar EOL (baz EOL ε))
=比;
foo EOL (bar EOL (baz EOL ε))
=>
foo EOL (bar EOL "baz")
=>
foo EOL "barbaz"
=>
"foobarbaz"
尽管嵌套在我使用括号时很明显,但这与LR堆栈在解析过程中出现的方式非常接近(不包括epsilon规则的显式出现;如果当前的forward属于FOLLOW(messages)
,那么这个reduce(生成NULL
)将被执行。