24.8.1.5 Жизнь с слабым парсером

Техника парсинга, используемая 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" он находит. Это переносит работу по различению различных случаев на лексер, который таким образом должен будет смотреть на окружающий текст, чтобы найти специальные подсказки.