对于在返回字符串的规则中包含%empty的替代方法,正确的操作是什么?



我的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)将被执行。

相关内容

最新更新