Разбор строки antlr - PullRequest
       7

Разбор строки antlr

0 голосов
/ 27 ноября 2018

У меня есть строки в качестве правила синтаксического анализа, а не лексера, потому что строки могут содержать escape-символы с выражениями в них, например "The variable is \(variable)".

string
 : '"' character* '"'
 ;

character
 : escapeSequence
 | .
 ;

escapeSequence
 : '\(' expression ')'
 ;

IDENTIFIER
 : [a-zA-Z][a-zA-Z0-9]*
 ;

WHITESPACE
 : [ \r\t,] -> skip
 ;

Это не работает, потому что . соответствует любому токенуа не какой-либо символ, так много идентификаторов будут сопоставлены, и пробелы будут полностью игнорироваться.

Как я могу разобрать строки, которые могут иметь выражения внутри них?

Просмотр парсера для Swift иJavascript, оба языка, которые поддерживают такие вещи, я не могу понять, как они работают.Из того, что я могу сказать, они просто выводят строку типа «моя строка с (переменными) в ней», фактически не имея возможности проанализировать переменную как ее собственную вещь.

1 Ответ

0 голосов
/ 28 ноября 2018

Эта проблема может быть решена с помощью лексических режимов, если один режим для внутренней части строк и один (или более) для внешней.Наблюдение " снаружи переключится на внутренний режим, а наблюдение \( или " переключится обратно наружу.Единственной сложной частью было бы видеть ) снаружи: иногда он должен переключаться обратно внутрь (потому что он соответствует \(), а иногда не должен (когда он соответствует простой ()).

Самый простой способ добиться этого будет выглядеть так:

Lexer:

lexer grammar StringLexer;

IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* ;
DQUOTE: '"' -> pushMode(IN_STRING);
LPAR: '(' -> pushMode(DEFAULT_MODE);
RPAR: ')' -> popMode;

mode IN_STRING;

TEXT: ~[\\"]+ ;

BACKSLASH_PAREN: '\\(' -> pushMode(DEFAULT_MODE);

ESCAPE_SEQUENCE: '\\' . ;

DQUOTE_IN_STRING: '"' -> type(DQUOTE), popMode;

Parser:

parser grammar StringParser;

options {
    tokenVocab = 'StringLexer';
}

start: exp EOF ;

exp : '(' exp ')'
    | IDENTIFIER
    | DQUOTE stringContents* DQUOTE
    ;

stringContents : TEXT
               | ESCAPE_SEQUENCE
               | '\\(' exp ')'
               ;

Здесь мынажимайте режим по умолчанию каждый раз, когда мы видим ( или \(, и нажимайте на режим каждый раз, когда мы видим ).Таким образом, он будет возвращаться внутрь строки только в том случае, если режим на вершине стека является строковым режимом, что будет иметь место только в том случае, если с момента последнего \(.

не останется незамеченных (.

Этот подход работает, но имеет недостаток в том, что несогласованный ) вызовет исключение пустого стека, а не нормальную синтаксическую ошибку, потому что мы вызываем popMode для пустого стека.


Чтобы избежать этого, мы можем добавить элемент, который отслеживает, насколько глубоко мы вложены в скобки, и не выталкивает стек, когда уровень вложенности равен 0 (т. Е. Если стек пуст):

@members {
    int nesting = 0;
}

LPAR: '(' {
    nesting++;
    pushMode(DEFAULT_MODE);
};
RPAR: ')' {
    if (nesting > 0) {
        nesting--;
        popMode();
    }
};

mode IN_STRING;

BACKSLASH_PAREN: '\\(' {
    nesting++;
    pushMode(DEFAULT_MODE);
};

(Части, которые я пропустил, такие же, как в предыдущей версии).

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


Если вы хотите избежать действий, последняя альтернатива будет иметь три режима: один для кода, который находится вне каких-либо строк, один для внутренней части строки и один длявнутри \().Третий будет почти идентичен внешнему, за исключением того, что он будет толкать и выдвигать режим при отображении скобок, тогда как внешний - нет.Чтобы оба режима производили одинаковые типы токенов, все правила третьего режима будут вызывать type().Это будет выглядеть следующим образом:

lexer grammar StringLexer;

IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* ;
DQUOTE: '"' -> pushMode(IN_STRING);
LPAR: '(';
RPAR: ')';

mode IN_STRING;

TEXT: ~[\\"]+ ;

BACKSLASH_PAREN: '\\(' -> pushMode(EMBEDDED);

ESCAPE_SEQUENCE: '\\' . ;

DQUOTE_IN_STRING: '"' -> type(DQUOTE), popMode;

mode EMBEDDED;

E_IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]* -> type(IDENTIFIER);
E_DQUOTE: '"' -> pushMode(IN_STRING), type(DQUOTE);
E_LPAR: '(' -> type(LPAR), pushMode(EMBEDDED);
E_RPAR: ')' -> type(RPAR), popMode;

Обратите внимание, что теперь мы больше не можем использовать строковые литералы в грамматике синтаксического анализатора, поскольку строковые литералы нельзя использовать, когда несколько правил лексера определены с использованием одного и того же строкового литерала.Так что теперь мы должны использовать LPAR вместо '(' в парсере и т. Д. (Мы уже должны были сделать это для DQUOTE по той же причине).

Поскольку эта версия включает в себя многодублирование (особенно при увеличении количества токенов) и предотвращение использования строковых литералов в грамматике синтаксического анализатора. Обычно я предпочитаю версию с действиями.


Полный код для всех трех альтернатив также может бытьнайдено на GitHub .

...