Antlr4: как избежать избыточных семантических предикатов?

#java #antlr4

#java #antlr4

Вопрос:

Вот начало моих правил лексера:

 F_TEXT_START
    : {! matchingFText}? 'f"' {matchingFText = true;}
    ;

F_TEXT_PH_ESCAPE
    : {matchingFText amp;amp; ! matchingFTextPh}? '{=/'
    ;

F_TEXT_PH_START
    : {matchingFText amp;amp; ! matchingFTextPh}? '{=' {matchingFTextPh = true;}
    ;

F_TEXT_PH_END
    : {matchingFText amp;amp; matchingFTextPh}? '}' {matchingFTextPh = false;}
    ;

F_TEXT_CHAR
    : {matchingFText amp;amp; ! matchingFTextPh}? (~('"' | '{')  | '""' | '{' ~'=')
    ;

F_TEXT_END
    : {matchingFText amp;amp; ! matchingFTextPh}? '"' {matchingFText = false;}
    ;


IF
    : {! matchingFText || matchingFTextPh}? 'if'
    ;

ELIF
    : {! matchingFText || matchingFTextPh}? 'elif'
    ;

// Lots of other keywords

fragment LETTER
    : ('A' .. 'Z' | 'a' .. 'z' | '_')
    ;
    
VARIABLE
    : {! matchingFText || matchingFTextPh}? LETTER (LETTER | DIGIT)*
    ;
  

Что я делаю, так это помещаю свой форматированный текст не просто как обычный текстовый токен, а с буквой f перед, но я добавляю его в свое дерево синтаксического анализа, чтобы иметь возможность определить, есть ли ошибки при синтаксическом анализе (с помощью всего parser.start() ). Итак, форматированный текст начинается с f" , заканчивается на " , любой " должен быть заменен на "" и может содержать заполнители, начинающиеся с {= и заканчивающиеся на } , но если вы хотите на самом деле написать {= , вам придется заменить его на {=/ .
Проблема в том, что в обычном форматированном текстовом содержимом (не заполнителе) лексер начал обрабатывать не только F_TEXT_CHAR , но и другие правила лексера, такие как переменные. То, что я сделал, кажется довольно глупым, я просто поместил семантические предикаты для каждого другого правила, чтобы избежать их сопоставления в содержимом форматированного текста (но все еще в заполнителе).

Разве нет лучшего способа?

Ответ №1:

Я бы использовал для этого лексический режим. Чтобы использовать лексические режимы, вам придется определить отдельные грамматики лексера и синтаксического анализатора. Вот краткая демонстрация:

 lexer grammar TestLexer;

F_TEXT_START
 : 'f"' -> pushMode(F_TEXT)
 ;

VARIABLE
 : LETTER (LETTER | DIGIT)*
 ;

F_TEXT_PH_ESCAPE
 : '{=/'
 ;

F_TEXT_PH_END
 : '}' -> popMode
 ;

SPACES
 : [ trn]  -> skip
 ;

fragment LETTER
 : [a-zA-Z_]
 ;

fragment DIGIT
 : [0-9]
 ;

mode F_TEXT;

  F_TEXT_CHAR
   : ~["{]  | '""' | '{' ~'='
   ;

  F_TEXT_PH_START
    : '{=' -> pushMode(DEFAULT_MODE)
    ;

  F_TEXT_END
   : '"' -> popMode
   ;
  

Используйте лексер в своем анализаторе следующим образом:

 parser grammar TestParser;

options {
  tokenVocab=TestLexer;
}

// ...
  

Если вы теперь обозначите входные данные f"mu {=mu}" mu , вы получите следующие токены:

 F_TEXT_START              `f"`
F_TEXT_CHAR               `mu `
F_TEXT_PH_START           `{=`
VARIABLE                  `mu`
F_TEXT_PH_END             `}`
F_TEXT_END                `"`
VARIABLE                  `mu`
  

Комментарии:

1. Спасибо вам за это! Но я не понимаю: почему вы не поставили F_TEXT_PH_START в F_TEXT режим? Кстати, спасибо за ваше репозиторий GitHub «mu», это очень помогло!

2. Я думал, F_TEXT_PH_START что это может происходить только внутри форматированной строки. Если это не так, соответствующим образом скорректируйте грамматику моего примера.

3. Да, это так! Кроме того, он может объединяться в F_TEXT_CHAR вот так: F_TEXT_CHAR: ~["{] | '""' | '{' ~'=' | '{=/';

4. Кроме того, я уже использую { и } в своей грамматике по умолчанию (но это не может быть в expression , что я прошу в синтаксическом анализаторе для заполнителя. Каким будет семантический предикат для этого?

5. Я обнаружил семантическую префиксацию при чтении кода antlr: _modeStack.contains(1)