IDK, I just up read stuff about LR parsers to freshen up on the topic[1] and then intuited what the parser state actually looks like and why - the error message ended up very useful to be able to do this, containing a lot of the relevant details already (in slightly cryptic form)
Basically, you want to be able to start building up the outer_attr
or the inner_attr
from parts of it already being on the parser stack (thus making #
legal to be shifted), which means the single attr must be sandwiched between the previous known-inner attrs, and subsequent known-outer attrs in a way I'm which it can be reduced later.
That basically is what motivates the nesting or outer* outer inner inner*
so that when you're at outer* yet_unknown ...unparsed
and move on to complete it to outer* outer ...unparsed
then reduce the outer* outer
into a single outer*
or complete it to outer* inner ...unparsed
, moving on smoothly into "already being in the middle of the first item" which only works if you need none of these virtual empty inner*
before it..
..thinking about it now once again, I wonder:
maybe it also works, instead of changing the nesting, to just turn the attr*
-style structure into more of a (attr+)?
structure? This could also avoid the need for this virtual initial emptly-list-of-inner-attr early reduction, AFAICT.
E.g. if
outer_attrs:
| /* empty */
| outer_attrs HASH outer_attr
became something like
outer_attrs:
| /* empty */ { [ ] }
| inner_attrs_non_empty { $1 }
outer_attrs_non_empty:
| outer_attr { [ $1 ] }
| outer_attrs_non_empty HASH outer_attr { $3 :: $1 }
This still â again â would be a change inconsistently applied though, as the same thing on inner_attrs
would break it again. (But at least the [reversed] oderings are consistent again )
Still just intuiting, but for a more âconsistentâ alternative solution, treating the two the same, maybe it can work to include the optionality of the construct into the surrounding construct⌠e.g. only have never-empty lists of attributes
outer_attrs:
| outer_attr
| outer_attrs HASH outer_attr
outer_attr:
| LBRACK attr RBRACK
inner_attrs:
| inner_attr
| inner_attrs HASH inner_attr
inner_attr:
| EXCLAMATION LBRACK attr RBRACK
and then do stuff like
item:
| vis_item
| outer_attrs vis_item
expression:
| expression_no_block
| outer_attrs expression_no_block
and
safe_module:
| MOD ident SEMI
| MOD ident LBRACE items RBRACE
| MOD ident LBRACE inner_attrs items RBRACE
unsafe_module:
| safe_module
| UNSAFE MOD ident SEMI
| UNSAFE MOD ident LBRACE items RBRACE
| UNSAFE MOD ident LBRACE inner_attrs items RBRACE
Thatâs just me wondering though⌠it probably just unnecessarily complicates stuff⌠well ââ I guess it could be worth considering if you prefer the (reversed) order to be consistent between these attribute-lists; in which case youâd presumably only need it for outer_attrs
ââ for which I already determined that this sort of "inlining" aspect of it shouldnâŧ actually be necessary, ah, oh well⌠thatâs just back to the first suggestion for alternatives I have made in this reply, anyway
and learn some more things; and become annoyed about the hard-to-approach nature of most material about the topic online âŠď¸