Как разрешить двусмысленность в определении грамматики LR (1)?

Я пишу компилятор Golang в OCaml, и списки аргументов вызывают у меня небольшую головную боль. В Go можно группировать последовательные имена параметров одного типа следующим образом:

func f(a, b, c int)  ===  func f(a int, b int, c int)

вы также можете иметь список типов, без имен параметров:

func g(int, string, int)

два стиля не могут быть смешаны и сопоставлены; либо все параметры названы, либо нет.

моя проблема заключается в том, что когда парсер видит запятую, он не знает, что делать. В первый пример, это a имя типа или имя переменной с большим количеством переменных? Запятая играет двойную роль, и я не уверен, как это исправить.

Я использую инструмент генератора парсера Menhir для OCaml.

Edit: на данный момент моя грамматика менгира точно соответствует правилам, указанным в http://golang.org/ref/spec#Function_types

2 ответов


как написано, грамматика go не LALR(1). На самом деле, это не LR(k) любой k. Однако он однозначен, поэтому вы можете успешно проанализировать его с помощью GLR parser, если вы можете найти один (я уверен, что есть несколько генераторов парсера GLR для OCAML, но я не знаю достаточно о любом из них, чтобы рекомендовать один).

если вы не хотите (или не можете) использовать GLR парсер, вы можете сделать это так же, как Расс Кокс сделал в gccgo компилятор, который использует bison. (bison может генерировать Парсеры GLR, но Cox не использует эту функцию.) Его метод не основан на различении сканером названий типов и не названий типов.

скорее, он просто принимает списки параметров, элементами которых являются либо name_or_type или name name_or_type (на самом деле, есть больше возможностей, чем, что, из-за ... синтаксис, но он не изменяет общий принцип.) Это просто, недвусмысленно и LALR(1), но это чрезмерно-принятие - это будет принять func foo(a, b int, c), например -- и он не создает правильное абстрактное синтаксическое дерево, потому что он не присоединяет тип к списку объявляемых параметров.

это означает, что как только список аргументов полностью проанализирован и будет вставлен в AST, прикрепленный к некоторому объявлению функции (например), выполняется семантическое сканирование, чтобы исправить его и, при необходимости, создать сообщение об ошибке. Это сканирование выполняется справа налево по списку объявления элементы, так что указанный тип может быть распространен влево.

стоит отметить, что грамматика в справочном руководстве также чрезмерно приемлема, потому что она не выражает ограничение, что "либо все параметры названы, либо нет". Это ограничение мог бы быть выраженным в грамматике LR(1) - я оставлю это как упражнение для читателей-но полученная грамматика будет намного сложнее понять.


у вас нет двусмысленности. Тот факт, что стандартным парсером Go является LALR(1), доказывает это.

- это имя типа или имя переменной с большим количеством переменных до?

таким образом, в основном ваша грамматика и парсер в целом должны быть полностью отключены от таблицы символов; не будьте C – ваша грамматика не неоднозначна, поэтому вы можете проверить имя типа позже в AST.

это соответствующие правила (от http://golang.org/ref/spec); они уже верны.

Parameters     = "(" [ ParameterList [ "," ] ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] [ "..." ] Type .
IdentifierList = identifier { "," identifier } .

я объясню их тебе:

IdentifierList = identifier { "," identifier } .

фигурные скобки представляют собой закрытие kleene (в нотации регулярного выражения POSIX это звездочка). Это правило гласит: "имя идентификатора, необязательно сопровождаемое литеральной запятой и идентификатором, необязательно сопровождаемое литеральной запятой и идентификатором и т. д. ... ad infinitum"

ParameterDecl  = [ IdentifierList ] [ "..." ] Type .

квадратные скобки являются недействительными; это означает, что эта часть может присутствовать, а может и не присутствовать. (В нотации регулярного выражения POSIX это знак вопроса). Итак ,у вас есть " возможно, IdentifierList, за которым следует, возможно, многоточие, а затем тип.

ParameterList  = ParameterDecl { "," ParameterDecl } .

вы можете иметь несколько ParameterDecl в списке, например func x(a, b int, c, d string).

Parameters     = "(" [ ParameterList [ "," ] ] ")" .

эти правила определяют, что ParameterList является необязательным и должен быть окружен скобками и может включать необязательный конечный литерал запятой, полезный при написании чего-либо например:

func x(
    a, b int,
    c, d string, // <- note the final comma
)

грамматика Go переносима и может быть проанализирована любым парсером снизу вверх с одним маркером lookahead.


изменить в отношении "не будь C": я сказал это потому, что C - это контекстно-зависимая и как они решают эту проблему во многих (всех?) составители связывая таблицу символов, чтобы лексер и лексический анализ лексемы по-разному в зависимости от того, если они определяются как имена типов и переменных. Это хак и не должно быть сделано для однозначных грамматик!