Техника парсинга, используемая SMIE, не позволяет токенам вести себя по-разному в разных контекстах. Для большинства языков программирования это проявляется в конфликтах приоритетов при преобразовании грамматики BNF.
Иногда эти конфликты можно обойти, выразив грамматику немного иначе. Например, для Modula-2 может показаться естественным иметь грамматику BNF, выглядящую так:
...
(inst ("IF" exp "THEN" insts "ELSE" insts "END")
("CASE" exp "OF" cases "END")
...)
(cases (cases "|" cases)
(caselabel ":" insts)
("ELSE" insts))
...
Но это вызовет конфликты для "ELSE": с одной стороны, правило IF подразумевает (среди многих других вещей), что "ELSE" = "END"; но с другой стороны, поскольку "ELSE" появляется внутри cases, который находится слева от "END", у нас также есть "ELSE" > "END". Мы можем решить этот конфликт, используя:
...
(inst ("IF" exp "THEN" insts "ELSE" insts "END")
("CASE" exp "OF" cases "END")
("CASE" exp "OF" cases "ELSE" insts "END")
...)
(cases (cases "|" cases) (caselabel ":" insts))
...
или
...
(inst ("IF" exp "THEN" else "END")
("CASE" exp "OF" cases "END")
...)
(else (insts "ELSE" insts))
(cases (cases "|" cases) (caselabel ":" insts) (else))
...
Переработка грамматики с целью решения конфликтов имеет свои недостатки, потому что SMIE предполагает, что грамматика отражает логическую структуру кода, поэтому предпочтительнее сохранить BNF ближе к предполагаемому абстрактному синтаксическому дереву.
В других случаях, после тщательного рассмотрения, вы можете прийти к выводу,
что эти конфликты не являются серьезными и просто разрешить их с помощью
аргумента resolvers функции smie-bnf->prec2.
Обычно это происходит потому, что грамматика просто неоднозначна: конфликт
не влияет на набор программ, описываемых грамматикой, а только на способ
их парсинга. Это типично для разделителей и ассоциативных бинарных
операторов, где вы хотите добавить решатель, такой как
'((assoc "|")). Другой случай, когда это может
произойти, — это классическая проблема висячего else,
где вы будете использовать '((assoc "else" "then")).
Это также может произойти в случаях, когда конфликт реален и действительно
не может быть разрешен, но это вряд ли вызовет проблемы на практике.
Наконец, во многих случаях некоторые конфликты останутся, несмотря на все
попытки реструктурировать грамматику. Не отчаивайтесь: хотя парсер не
может стать умнее, вы можете сделать лексер настолько умным, насколько
хотите. Таким образом, решение состоит в том, чтобы рассмотреть токены,
вовлеченные в конфликт, и разделить один из этих токенов на 2 (или более)
различных токенов. Например, если грамматика должна различать два несовместимых
использования токена "begin", сделайте так, чтобы
лексер возвращал разные токены (например, "begin-fun"
и "begin-plain") в зависимости от того, какой
тип "begin" он находит. Это переносит работу по
различению различных случаев на лексер, который таким образом должен будет
смотреть на окружающий текст, чтобы найти специальные подсказки.