I'm assuming that by "doesn't work", you mean that bison reports a shift/reduce conflict. If you go ahead and use the generated parser anyway, then it will not parse correctly in many cases, because the conflict is real and cannot be resolved by any static rule.
The issue is simple. Remember that a LALR(1) bottom-up parser like the one generate by bison performs every reduction exactly at the end of the right-hand side, taking into account only the next token (the "lookahead token"). So it must know which production to use at the moment the production is completely read. (That gives it a lot more latitude than a top-down parser, which needs to know which production it will use at the beginning of the production. But it's still not always enough.)
The problematic case is the production ID with without
. Here, whatever input matches with
needs to be reduced to a single non-terminal with
before the continues with without
. To get to this point, the parser must have passed over some number of '[' INDEX ']'
dimensions, and the lookahead token must be [
, regardless of whether the next dimension has a definite size or not.
If the with
rule is right-recursive:
with: '[' INDEX ']' with
| '[' INDEX ']'
then the parser is really stuck. If what follows has a definite dimension, it needs to continue trying the first production, which means shifting the [
. If what follows has no INDEX
, it needs to reduce the second production, which will trigger a chain of reductions leading back to the beginning of the list of dimensions.
On the other hand, with a left recursive rule:
with: with '[' INDEX ']'
| '[' INDEX ']'
the parser has no problem at all, because each with
is reduced as soon as the ]
is seen. That means that the parser doesn't have to know what follows in order to decide to reduce. It decides between the two rules based on the past, not the future: the first dimension in the array
uses the second production, and the remaining ones (which follow a with
) use the first one.
That's not to say that left-recursion is always the answer, although it often is. As can be seen in this case, right-recursion of a list means that individual list elements pile up on the parser stack until the list is eventually terminated, while left-recursion allows the reductions to happen immediately, so that the parser stack doesn't need to grow. So if you have a choice, you should generally prefer left-recursion.
But sometimes right-recursion can be convenient, particularly in syntaxes like this where the end of the list is different from the beginning. Another way of writing the grammar could be:
array : ID dims
dims : without
| '[' INDEX ']'
| '[' INDEX ']' dims
without: '[' ']'
| '[' ']' without
Here, the grammar only accepts empty dimensions at the end of the list because of the structure of dims
. But to achieve that effect, dims
must be right-recursive, since it is the end of the list which has the expanded syntax.