基础用法
.y
每个 Bison 文件由 %%
分成三部分。
%{
##include <stdio.h>
/* 这里是序曲 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的开头 */
int yylex(void);
void yyerror(const char *s);
%}
/* 这些地方可以输入一些 bison 指令 */
/* 比如用 %start 指令指定起始符号,用 %token 定义一个 token */
%start reimu
%token REIMU
%%
/* 从这里开始,下面是解析规则 */
reimu : marisa { /* 这里写与该规则对应的处理代码 */ puts("rule1"); }
| REIMU { /* 这里写与该规则对应的处理代码 */ puts("rule2"); }
; /* 规则最后不要忘了用分号结束哦~ */
/* 这种写法表示 ε —— 空输入 */
marisa : { puts("Hello!"); }
%%
/* 这里是尾声 */
/* 这部分代码会被原样拷贝到生成的 .c 文件的末尾 */
int yylex(void)
{
int c = getchar(); // 从 stdin 获取下一个字符
switch (c) {
case EOF: return YYEOF;
case 'R': return REIMU;
default: return 0; // 返回无效 token 值,迫使 bison 报错
}
}
void yyerror(const char *s)
{
fprintf(stderr, "%s\n", s);
}
int main(void)
{
yyparse(); // 启动解析
return 0;
}
另外有一些值得注意的点:
- Bison 传统上将 token 用大写单词表示,将 symbol 用小写字母表示。
- Bison 能且只能生成解析器源代码(一个
.c
文件),并且入口是yyparse
,所以为了让程序能跑起来,你需要手动提供main
函数(但不一定要在.y
文件中——你懂“链接”是什么,对吧?)。 - Bison 不能检测你的 action code 是否正确——它只能检测文法的部分错误,其他代码都是原样粘贴到
.c
文件中。 - Bison 需要你提供一个
yylex
来获取下一个 token。 - Bison 需要你提供一个
yyerror
来提供合适的报错机制。
顺便提一嘴,上面这个 .y
是可以工作的——尽管它只能接受两个字符串。把上面这段代码保存为 reimu.y
,执行如下命令来构建这个程序:
编译运行
$ bison reimu.y
$ gcc reimu.tab.c
$ ./a.out
R<-- 不要回车在这里按 Ctrl-D
rule2
$ ./a.out
<-- 不要回车在这里按 Ctrl-D
Hello!
rule1
$ ./a.out
blablabla <-- 回车或者 Ctrl-D
Hello!
rule1 <-- 匹配到了 rule1
syntax error <-- 发现了错误
于是我们验证了上述代码的确识别了该文法定义的语言 { "", "R" }
。
一个好教程:https://www.oreilly.com/library/view/flex-bison/9780596805418/ch01.html