Как ANTLR решает, должны ли терминалы быть разделены пробелами или нет?

Пишу лексический анализатор на Swift для Swift. Я использовал грамматику ANTLR, но столкнулся с проблемой, что не понимаю, как ANTLR решает, следует ли разделять терминалы пробелами.

Вот грамматика: https://github.com/antlr/grammars-v4/blob/master/swift/Swift.g4

Предположим, у нас есть кастинг в Swift. Он также может работать с необязательными типами (Int?, String?) и с необязательными типами (Int, String). Вот допустимые примеры: "as?Int", "as?Int", "as?Int". Недопустимые примеры: "asInt" (это не приведение). Я реализовал логику, когда терминалы в правилах грамматики могут быть разделены 0 или более символами WS (пробелами). Но с этой логикой «asInt» соответствует приведению, потому что он содержит «as» и тип «Int» и имеет 0 или более символов WS. Но он должен быть недействительным.

Грамматика Swift содержит следующие правила:

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. Я прав?
  2. Что я пропустил?

Спасибо.


person artyom.razinov    schedule 19.03.2016    source источник


Ответы (1)


Вот правило ANTLR 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 находится на отдельном канале.

person Lucas Trzesniewski    schedule 19.03.2016
comment
Вау, спасибо. Я думал, что правила лексера и парсера работают одновременно при создании дерева токенов. - person artyom.razinov; 19.03.2016
comment
Нет, это отдельные этапы синтаксического анализа, что делает концепцию довольно простой для понимания, при этом хорошо играя с большинством языков. Но синтаксический анализ языков, которым нужна информация из парсера в лексере, затруднен. - person Lucas Trzesniewski; 19.03.2016
comment
Большое тебе спасибо! Вы помогли мне написать мой инструмент. Вот результат: pastebin.com/xvFWnhFC Я так счастлив. - person artyom.razinov; 30.03.2016
comment
Здорово, рад, что смог помочь :) - person Lucas Trzesniewski; 30.03.2016