如何通过Bison规则将INTEGER信息传递至names规则的动作中?
这个问题的核心在于Bison的自底向上解析特性:你的names规则会在匹配完成后立刻执行动作,而此时INTEGER还没被解析(因为它在names之后),所以names的动作里无法直接访问INTEGER的值。要实现把每个ID和INTEGER一起打印的需求,有两种常用的解决方案,我分别给你详细说明:
方法1:调整规则顺序,通过参数传递INTEGER值(推荐)
如果你的语法允许调整INTEGER和names的顺序,这种方法最简洁直接。我们可以给names规则添加参数,把提前解析好的INTEGER值传递进去,让names的每个动作都能直接使用这个值。
示例代码
首先是词法分析器(flex代码):
%{ #include "parser.tab.h" %} %% [a-zA-Z_][a-zA-Z0-9_]* { yylval.str = strdup(yytext); return ID; } [0-9]+ { yylval.num = atoi(yytext); return INTEGER; } [ \t\n] ; // 忽略空白字符 . { return yytext[0]; } %%
然后是Bison解析器代码:
%{ #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义语义值的联合体 typedef union { int num; char *str; } YYSTYPE; #define YYSTYPE_IS_DECLARED 1 %} %token ID INTEGER %type <num> INTEGER %type <void> names(int) // 声明names接受一个int类型参数,无返回值 %% // 调整规则顺序:先解析INTEGER,再把它的值传给names rule1: INTEGER names($1) { /* do something1 可以在这里做后续处理 */ } ; // names规则使用传入的val参数和ID一起打印 names(int val): ID { // do something2:打印当前ID和对应的INTEGER值 printf("ID: %s, INTEGER: %d\n", $1, val); free($1); // 释放词法分析器分配的内存 } | names(val) ID { // do something2:打印新匹配的ID和INTEGER值 printf("ID: %s, INTEGER: %d\n", $2, val); free($2); } ; %% int yyerror(const char *msg) { fprintf(stderr, "Error: %s\n", msg); return 1; } int main() { return yyparse(); }
原理说明
Bison支持给非终结符添加参数,我们先解析INTEGER得到它的语义值,再将这个值作为参数传递给names规则。这样names的每个分支动作都能直接使用这个参数,和ID一起输出。
方法2:收集ID列表,在rule1动作中统一打印(无需调整规则顺序)
如果必须保持names INTEGER的原始规则结构,我们可以让names规则收集所有匹配到的ID,把它们存储成一个链表(或动态数组),然后在rule1的动作里,遍历这个列表并和INTEGER值一起打印。
示例代码
Bison解析器代码:
%{ #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义存储ID的链表节点结构 typedef struct Node { char *id; struct Node *next; } Node; typedef union { int num; char *str; Node *list; } YYSTYPE; #define YYSTYPE_IS_DECLARED 1 %} %token ID INTEGER %type <num> INTEGER %type <list> names %% // 保持原始规则顺序:names在前,INTEGER在后 rule1: names INTEGER { // do something1:遍历names返回的ID列表,逐个和INTEGER值打印 Node *current = $1; while (current != NULL) { printf("ID: %s, INTEGER: %d\n", current->id, $2); // 释放内存,避免泄漏 Node *temp = current; current = current->next; free(temp->id); free(temp); } } ; // names规则负责收集所有ID,返回链表头指针 names: ID { // 创建新节点存储当前ID Node *node = malloc(sizeof(Node)); node->id = $1; node->next = NULL; $$ = node; // 将节点作为names的语义值返回 } | names ID { // do something2:把新ID添加到链表末尾 Node *current = $1; while (current->next != NULL) { current = current->next; } Node *node = malloc(sizeof(Node)); node->id = $2; node->next = NULL; current->next = node; $$ = $1; // 返回整个链表的头指针 } ; %% int yyerror(const char *msg) { fprintf(stderr, "Error: %s\n", msg); return 1; } int main() { return yyparse(); }
原理说明
这种方法让names规则在匹配过程中把所有ID存储成链表,等到rule1匹配完成后,我们已经拿到了完整的ID列表和INTEGER值,此时就可以统一遍历打印了。缺点是需要手动管理链表的内存,代码稍复杂,但能保持原始的语法结构。
两种方法各有适用场景:如果你的语法允许调整顺序,方法1更简洁高效;如果必须保持names INTEGER的结构,方法2是更合适的选择。无论哪种方式,都要注意内存管理,避免泄漏。
内容的提问来源于stack exchange,提问作者Haloha

