使用lex/yacc编写语法时,Yacc中EBNF分组的初始化方法咨询
嘿,刚好我对Yacc和EBNF的适配问题挺熟的,咱们一步步理清楚你遇到的疑问:
首先得澄清个概念:Yacc本身的语法规则里并没有直接支持EBNF里的{...}(0次或多次重复)和[...](可选)语法,咱们得用Yacc的基础规则来模拟这些结构——你提到可选语法[...]用|实现,其实就是加个空分支(比如opt_item : item | /* 空规则 */),那对于EBNF里{...}代表的重复分组,咱们可以用递归规则来搞定,下面详细说:
1. 先搞懂:你说的“分组语法{.....}”到底指啥?
EBNF里的{ X }其实是说X这个序列可以重复0次或多次,而“分组”就是把多个符号打包成一个整体,再给它加重复规则,比如{ a b c }就是a b c这一串可以重复0次、1次、N次。在Yacc里要实现这种分组,第一步得把这个分组定义成一个单独的非终结符,再对这个非终结符做重复处理。
2. 模拟EBNF{...}(重复分组)的两种实用方式
方式一:用递归规则直接定义重复
比如你想实现{ expr '+' expr }这种重复分组,咱们先把单个分组单元定义成非终结符,再写重复规则:
%token EXPR PLUS /* 先定义单个"expr + expr"的分组单元 */ expr_plus_group : EXPR PLUS EXPR ; /* 再定义0次或多次的重复:空规则对应0次,递归调用对应多添加一个分组 */ expr_plus_repeat : /* 空规则,代表0次出现 */ | expr_plus_repeat expr_plus_group ;
这里的expr_plus_repeat就完全等价于EBNF里的{ expr '+' expr }了。
方式二:用左递归(更适配Yacc的LR分析器)
如果重复的分组是和前面的内容结合的(比如咱们写表达式语法时常见的后缀重复),用左递归效率会更高,比如实现类似expr { '+' expr }(也就是表达式后面跟0个或多个+ expr):
expr : EXPR | expr PLUS EXPR ;
这其实就是写表达式语法时最常用的左递归写法,本质上就是模拟了EBNF中expr ( '+' expr )*(*和{...}是一个意思)的结构。
3. 关于“分组初始化”的误区
你提到可选语法[...]用|来“初始化”,其实本质是用|提供空分支来实现“可选”。而对于{...}的重复分组,不需要类似的“初始化”操作,而是通过空规则+递归来覆盖“0次”和“多次”的场景——空规则对应0次出现,递归调用对应每次新增一个分组实例。
4. 顺便给你个解决语法歧义的小提示
既然你现在遇到了语法歧义问题,除了用EBNF的结构来明确语法逻辑,还可以试试这些方法:
- 用
%left/%right给Yacc规则设置优先级,解决运算符之类的歧义 - 把复杂规则拆分成多个小的非终结符(也就是分组),减少规则的模糊性
- 运行Yacc的时候加
-v参数,生成y.output文件,查看冲突的状态,针对性调整规则
内容的提问来源于stack exchange,提问作者Burak

