Но я все еще не мог понять, почему она все еще вызывает эту ошибку, поскольку для меня совершенно ясно, что скобка, за которой следует жирная стрелка =>
, будет функцией ...
Да, это супер ясно. Грамматика совершенно однозначна. Но вы не ограничены просмотром одного входного токена за раз, тогда как анализатор LR ( 1 ) есть. В тот момент, когда парсер пытается решить, что делать с a
в (a)
, он еще не может видеть жирную стрелку и должен принять решение, прежде чем это сделает. То есть, прежде чем использовать ) , парсер должен решить, будет ли то, что предшествует, expr
или separated_nonempty_list
.
Возможно, стоит отметить, что на самом деле грамматика - это LR (2): еще один знак ожидания, и конфликт может быть разрешен. Это не слишком утешительно, поскольку Menhir не предоставляет какого-либо механизма для расширенного просмотра, но это означает, что решение существует, поскольку существование грамматики LR (k) для языка подразумевает существование грамматики LR (1) для той же самой язык; есть даже механический алгоритм для создания грамматики LR (1).
Вместо того, чтобы преобразовывать всю грамматику, единственное немного грязное решение состоит в том, чтобы выделить случаи «(a)», что можно сделать с помощью пары явно избыточных правил:
expr: LPAREN ID RPAREN ARROW expr
| LPAREN ID RPAREN
Второе производство явно конфликтует с LPAREN expr RPAREN
, но это конфликт сдвига / уменьшения, а не конфликта уменьшения / уменьшения, и его можно разрешить, задав RPAREN
более высокий приоритет, чем ID
, чтобы принудительно разрешить разрешение в в пользу смещения RPAREN
.
Это полное искажение деклараций предшествования, и оно может оказаться проблематичным, поскольку ваша грамматика усложняется. Более теоретически обоснованным решением было бы определение expr_other_than_identifier
. Вы можете найти пример этого здесь , который является очень похожей грамматикой, и в некоторых других SO-ответах на подобные вопросы.
В частности, собственной грамматикой Yacc является LR (2) (вы не можете сказать, что правило закончилось, пока не увидите :
, следующий за нетерминалом, который запускает следующее правило). Аналогичное решение существует для этой грамматики, но большинство генераторов синтаксических анализаторов, подобных yacc, решают эту проблему, добавляя дополнительный взгляд в анализатор лексеров. Например, вы можете распознать ) =>
как один токен (включая внутренний пробел), или вы можете выдать другой токен с закрывающей скобкой, если следующий токен был жирной стрелкой.