Как решить проблему с ключевыми словами в качестве идентификаторов в грамматическом наборе
Я пытался написать грамматику языка graphql для grammarkit, и я довольно долго застрял в проблеме неоднозначности. Ключевые слова в graphql (такие как: type
, implements
, scalar
) также могут быть именами типов или полей. IE
type type implements type {}
Сначала я определил эти ключевые слова как tokens
в БНФ, но это означало бы, что приведенный выше случай недействителен. Но если я напишу эти ключевые слова напрямую, как я описываю правило, это приводит к двусмысленности в грамматике. Пример проблемы, которую я вижу на основе этой грамматики ниже, если вы определите что-то вроде этого
directive @foo on Baz | Bar
scalar Foobar @cool
зритель PSI говорит мне, что в положении @cool
это ожидает DirectiveAddtlLocation
Это правило, которое я даже не упоминаю в скалярном правиле. Кто-нибудь знаком с grammarkit и сталкивался с чем-то подобным? Я бы очень признателен за понимание. Благодарю вас.
Вот отрывок грамматики для примера ошибки, который я упоминал выше.
{
tokens=[
LEFT_PAREN='('
RIGHT_PAREN=')'
PIPE='|'
AT='@'
IDENTIFIER="regexp:[_A-Za-z][_0-9A-Za-z]*"
WHITE_SPACE = 'regexp:\s+'
]
}
Document ::= Definition*
Definition ::= DirectiveTypeDef | ScalarTypeDef
NamedTypeDef ::= IDENTIFIER
// I.E. @foo @bar(a: 10) @baz
DirectivesDeclSet ::= DirectiveDecl+
DirectiveDecl ::= AT TypeName
// I.E. directive @example on FIELD_DEFINITION | ARGUMENT_DEFINITION
DirectiveTypeDef ::= 'directive' AT NamedTypeDef DirectiveLocationsConditionDef
DirectiveLocationsConditionDef ::= 'on' DirectiveLocation DirectiveAddtlLocation*
DirectiveLocation ::= IDENTIFIER
DirectiveAddtlLocation ::= PIPE? DirectiveLocation
TypeName ::= IDENTIFIER
// I.E. scalar DateTime @foo
ScalarTypeDef ::= 'scalar' NamedTypeDef DirectivesDeclSet?
1 ответ
Как только ваша грамматика видит directive @TOKEN on IDENTIFIER
, он потребляет последовательность DirectiveAddtlLocation
, Каждый из них состоит из PIPE
с последующим IDENTIFIER
, Как вы заметили в своем вопросе, "ключевые слова" GraphQL - это на самом деле просто особые случаи идентификаторов. Так что, вероятно, здесь происходит то, что, поскольку вы допускаете любой токен в качестве идентификатора, scalar
а также Foobar
оба потребляются как DirectiveAddtlLocation
и это никогда не получится увидеть ScalarTypeDef
,
# Parses the same as:
directive @foo on Bar | Baz | scalar | Foobar
@cool # <-- ?????
Вы можете обойти это, перечислив явный набор разрешенных директивных положений в вашей грамматике. (Возможно, вы даже сможете продвинуться далеко вперед, просто скопировав грамматику в Приложении B спецификации GraphQL и изменив ее синтаксис.)
DirectiveLocation ::= ExecutableDirectiveLocation | TypeSystemDirectiveLocation
ExecutableDirectiveLocation ::= 'QUERY' | 'MUTATION' | ...
TypeSystemDirectiveLocation ::= 'SCHEMA' | 'SCALAR' | ...
Теперь, когда вы идете в разборе:
directive @foo on QUERY | MUTATION
# "scalar" is not a directive location, so the DirectiveTypeDef must end
scalar Foobar @cool
(Несмотря на то, что различие между "идентификатором" и "ключевым словом" немного странно, я почти уверен, что грамматика GraphQL на самом деле не является неоднозначной; в любом контексте, где разрешен идентификатор в свободной форме, перед "стоит пунктуация" ключевое слово "может появиться снова, и в подобных случаях есть однозначные списки не совсем ключевых слов, которые не пересекаются.)