Как ANTLR решает, должны ли терминалы быть разделены пробелами или нет?
Я пишу лексический анализатор в Swift для Swift. Я использовал грамматику ANTLR, но столкнулся с проблемой, заключающейся в том, что я не понимаю, как ANTLR решает, должны ли терминалы быть разделены пробелами.
Вот грамматика: https://github.com/antlr/grammars-v4/blob/master/swift/Swift.g4
Предположим, у нас есть кастинг в Swift. Он также может работать с необязательными типами (Int?, String?) И с необязательными типами (Int, String). Вот допустимые примеры: "как? Int", "как Int", "как?Int". Неверные примеры: "asInt" (это не приведение). Я реализовал логику, когда терминалы в правилах грамматики могут быть разделены 0 или более символами WS (пробелами). Но с этой логикой asInt соответствует приведению, потому что он содержит "as" и тип "Int" и имеет 0 или более символов WS. Но это должно быть недействительным.
Свифт грамматика содержит эти правила:
DOT : '.' ;
LCURLY : '{' ;
LPAREN : '(' ;
LBRACK : '[' ;
RCURLY : '}' ;
RPAREN : ')' ;
RBRACK : ']' ;
COMMA : ',' ;
COLON : ':' ;
SEMI : ';' ;
LT : '<' ;
GT : '>' ;
UNDERSCORE : '_' ;
BANG : '!' ;
QUESTION: '?' ;
AT : '@' ;
AND : '&' ;
SUB : '-' ;
EQUAL : '=' ;
OR : '|' ;
DIV : '/' ;
ADD : '+' ;
MUL : '*' ;
MOD : '%' ;
CARET : '^' ;
TILDE : '~' ;
Кажется, что все эти терминалы могут быть разделены другими с 0 символами WS, а другие нет (например, "как" + идентификатор).
Я прав? Если я прав, проблема решена. Но может быть более сложная логика.
Теперь, если у меня есть правила
WS : [ \n\r\t\u000B\u000C\u0000]+
a : 'str1' b
b : 'str2' c
c : '+' d
d : 'str3'
Я использую их, как если бы они были этими правилами:
WS : [ \n\r\t\u000B\u000C\u0000]+
a : WS? 'str1' WS? 'str2' WS? '+' WS? 'str3' WS?
И я полагаю, что они должны быть такими (я не знаю, и это вопрос):
WS : [ \n\r\t\u000B\u000C\u0000]+
a: 'str1' WS 'str2' WS? '+' WS? 'str3'
(обратите внимание, что WS не является обязательным между 'str1' и 'str2')
Итак, есть 2 вопроса:
- Я прав?
- Что я пропустил?
Благодарю.
1 ответ
Вот АНТЛР WS
правило в вашей грамматике Swift:
WS : [ \n\r\t\u000B\u000C\u0000]+ -> channel(HIDDEN) ;
-> channel(HIDDEN)
инструкция говорит лексеру поместить эти токены в отдельный канал, чтобы анализатор их вообще не видел. Вы не должны засорять свою грамматику WS
правила - это стало бы нечитаемым.
ANTLR работает в два этапа: у вас есть лексер и парсер. Лексер создает токены, и синтаксический анализатор пытается определить конкретное синтаксическое дерево из этих токенов и грамматики.
Лексер в ANTLR работает так:
- Поглощайте персонажей, если они соответствуют любому правилу лексера.
- Если несколько правил соответствуют тексту, который вы использовали, используйте первое, которое появляется в грамматике
- Буквальные строки в грамматике (например,
'as'
) превращаются в неявные правила лексера (эквивалентноTOKEN_AS: 'as';
кроме названия будет просто'as'
). Они оказываются первыми в списке правил лексера.
Пример 1
Давайте посмотрим последствия этого при лексинге as?Int
(с пробелом в конце):
a
... потенциально совпадаетIdentifier
а также'as'
as
... потенциально совпадаетIdentifier
а также'as'
as?
не соответствует ни одному правилу лексера
Поэтому вы потребляете as
, который станет жетоном. Теперь вы должны решить, какой тип токена будет. И то и другое Identifier
а также 'as'
правила совпадают. 'as'
является неявным правилом лексера и считается первым в грамматике, поэтому оно имеет приоритет. Лексер испускает токен с текстом as
типа 'as'
,
Следующий токен.
?
... потенциально соответствуетQUESTION
правило?I
не соответствует ни одному правилу
Поэтому вы потребляете ?
из ввода и выдать токен типа QUESTION
с текстом ?
,
Следующий токен.
I
... потенциально совпадаетIdentifier
In
... потенциально совпадаетIdentifier
Int
... потенциально совпадаетIdentifier
Int
(сопровождается пробелом) ничего не соответствует
Поэтому вы потребляете Int
из ввода и выдать токен типа Identifier
с текстом Int
,
Следующий токен.
- У вас там есть место, оно соответствует
WS
править.
Вы потребляете это пространство и излучаете WS
знак на HIDDEN
канал. Парсер этого не увидит.
Пример 2
Теперь посмотрим как asInt
является токенизированным
a
... потенциально совпадаетIdentifier
а также'as'
as
... потенциально совпадаетIdentifier
а также'as'
asI
... потенциально совпадаетIdentifier
asIn
... потенциально совпадаетIdentifier
asInt
... потенциально совпадаетIdentifier
asInt
сопровождаемый пробелом не соответствует ни одному правилу лексера.
Поэтому вы потребляете asInt
из входного потока, и испускают Identifier
токен с текстом asInt
,
Парсер
Этап парсера интересует только типы токенов, которые он получает. Неважно, какой текст они содержат. Токены вне канала по умолчанию игнорируются, что означает следующие входные данные:
as?Int
- токены:'as'
QUESTION
Identifier
as? Int
- токены:'as'
QUESTION
WS
Identifier
as ? Int
- токены:'as'
WS
QUESTION
WS
Identifier
Все это приведет к тому, что парсер увидит следующие типы токенов: 'as'
QUESTION
Identifier
, как WS
находится на отдельном канале.