递归的语法规则
为了分析不定长的项目列表,你需要使用递归规则,也就是用自身来定义自己。例如,下面这个例子分析一个可能为空的数字列表:
numberlist: /* 空规则 */
| numberlist NUMBER
;
递归规则的实现完全依赖于具体需要分析的语法。下面这个例子分析一个通过逗号分隔的不为空的表达式列表,其中的expr在语法的其他地方已经被定义:
exprlist: expr
: exprlist ',' expr
;
也可能存在交互的递归规则,它们彼此引用对方:
exp: term
| term '+' term
;
term: '(' exp ')'
| VARIABLE
;
任何递归规则或者交互递归规则组里的每个规则都必须至少有一条非递归的分支(不指向自身);否则,将没有任何途径来终止它所比匹配的字符串,这是一个错误。
左递归和右递归
当你编写一个递归规则时,你可以把递归的引用放在规则右部的左端或者右端,例如:
exprlist: exprlist ',' expr; /* 左递归 */
exprlist: expr ',' exprlist; /* 右递归 */
大多数情况下,你可以选择任意一种方式来编写语法。bison处理左递归要比处理右递归更有效率。这是因为它的内部堆栈需要追踪到目前位置所有还处在分析中规则的全部符号。
如果使用右递归,而且有个表达式包含了10个子表达式,当读取第10个表达式的时候,堆栈中会有20个元素:10个表达式各自有expr和逗号。当表达式结束时,所有嵌套的exprlist都需要按照从右向左的顺序来规约。另一个方面,如果你使用左递归的版本,exprlist将在每个expr之后进行规约,这样内部堆栈中列表将永远不会超过三个元素。
具有10个元素的表达式列表不会对语法分析器造成什么问题。但是我们的语法经常需要分析拥有成千上万的元素的列表,尤其是当程序被定义为语句的列表时:
%start program
%%
program: statementlist;
statementlist: statement
| statementlist ';' statement
statement: ...
这个例子中,假定有一个5000条语句的程序需要分析,那么列表中将包括语句和分号10000个元素,而右递归版本将耗费太多的内存空间。
当你确定列表中的元素个数很少而且你需要把它们放到一个链表中,右递归语法就比较有用:
thinglist: THING { $$ = $1; }
| THING thinglist { $1->next = $2; $$ = $1; }
;
如果你对这个例子使用左递归语法的话,你就必须在最后把链表中倒序的内容在一次翻转,如果你需要在每次添加是找到链表的末端。
你可以定义YYINITDEPTH来控制语法分析器堆栈的长度,表明堆栈的初始大小,通常为200,也可以定义YYMAXDEPTH来设置堆栈长度的最大值,通常为1000。例如:
%{
#define YYMAXDEPTH 50000
%}
每个堆栈元素的大小是语义值的大小(%union元素的最大长度)加上2个字节的记号编号,如果你使用位置信息,还要加上16个字节。在具有上千MB虚拟内存的工作站上100000元素的堆栈大致需要2到3MB,但在更小的嵌入式系统中,你可能会希望重写你的语法来减少堆栈大小。