Сопоставление круглых скобок в ANTLR

#parsing #antlr #antlr4

#синтаксический анализ #antlr #antlr4

Вопрос:

Я новичок в Antlr, и недавно я столкнулся с одной проблемой, связанной с сопоставлением скобок. Каждый узел в дереве синтаксического анализа имеет вид (Node1,W1,W2,Node2) , где Node1 и Node2 — два узла, а W1 и W2 — два веса между ними. Учитывая входной файл as (1,C,10,2).((2,P,2,3).(3,S,3,2))*.(2,T,2,4) , дерево синтаксического анализа выглядит неправильно, где оператор не является родительским для этих узлов, а круглые скобки не совпадают.введите описание изображения здесь

Файл синтаксического анализа, который я написал, выглядит следующим образом:

 grammar Semi;

prog
    : expr 
    ;
    
expr
    : expr '*'   
    | expr ('.'|' ') expr 
    | tuple   
    | '(' expr ')' 
    ;

tuple
    : LP NODE W1 W2 NODE RP
    ;

LP  : '(' ;
RP  : ')' ;
W1  : [PCST0];
W2  : [0-9] ;
NODE: [0-9] ;


WS  : [ trn]  -> skip ;    // toss out whitespace
COMMA: ',' -> skip;
 

Похоже expr| '(' expr ')' , что это работает некорректно. Итак, что я должен сделать, чтобы этот анализатор определял, принадлежат ли круглые скобки узлу или нет?
Обновить:
В команде две ошибки:

 line 1:1 no viable alternative at input '(1'
line 1:13 no viable alternative at input '(2'
 

Похоже, что лексер не обнаружил кортежи, но почему это так?

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

1. Я подозреваю, что ваша проблема не имеет ничего общего с '(' expr ')' производством, а с вашими лексическими правилами. Если вы посмотрите на токены, сгенерированные для вашего ввода, вы, вероятно, обнаружите, что они не соответствуют вашим ожиданиям.

2. @sepp2k да, я только что понял это, но я не знаю, как это исправить .. Я также обнаружил ошибку «нет жизнеспособной альтернативы при вводе ‘(1’ » в команде

3. Прежде всего, у вас не должно быть нескольких правил лексера, которые соответствуют одному и тому же шаблону. Это просто приводит к тому, что один из них мертв. Тогда другая проблема заключается в том, что один 0 сам по себе создаст W1 токен, но, по-видимому, вы также хотите разрешить 0s как W2 или узлы. Самым простым решением для этого, вероятно, является наличие специального правила лексера только для '0' , которое можно использовать везде W1 , W2 где может использоваться узел или.

4. @sepp2k омг большое вам спасибо! Я просто пытался использовать только один лексер для чисел, и теперь он работает отлично. У меня просто есть еще один вопрос относительно вашего ответа, если я использую запятую в качестве разделителей, можно ли избежать этой проблемы с одним 0? Похоже, это так в дереве синтаксического анализа вывода, но я не уверен.

5. Проблема с 0 заключается в том, что входные данные, такие как (0, 0, 0, 0) , которые должны быть действительными в соответствии с моим пониманием вашего формата, будут обозначаться как LP W1 W1 W1 W1 RP , что не соответствует вашему tuple правилу. Теперь вы можете изменить кортеж, чтобы разрешить W1 везде, но тогда (P, P, P, P) это также будет разрешено, чего, я полагаю, вы не хотите. Поэтому я предлагаю изменить его так, чтобы он обозначался как . 0 12 P ZERO NUMBER W1_LETTER Тогда вы могли бы определить w2 и node (как правила синтаксического анализа, а не правила лексера) как ZERO | NUMBER и вы могли бы определить w1 как ZERO | W1_LETTER .

Ответ №1:

Ваши правила W2 и NODE одинаковы, поэтому узлы, которые вы намереваетесь сделать узлом, соответствуют W2.

опция grun with -tokens: (обратите внимание, нет токенов узла)

 [@0,0:0='(',<'('>,1:0]
[@1,1:1='1',<W2>,1:1]
[@2,3:3='C',<W1>,1:3]
[@3,5:6='10',<W2>,1:5]
[@4,8:8='2',<W2>,1:8]
[@5,9:9=')',<')'>,1:9]
[@6,10:10='.',<'.'>,1:10]
[@7,11:11='(',<'('>,1:11]
[@8,12:12='(',<'('>,1:12]
[@9,13:13='2',<W2>,1:13]
[@10,15:15='P',<W1>,1:15]
[@11,17:17='2',<W2>,1:17]
[@12,19:19='3',<W2>,1:19]
[@13,20:20=')',<')'>,1:20]
[@14,21:21='.',<'.'>,1:21]
[@15,22:22='(',<'('>,1:22]
[@16,23:23='3',<W2>,1:23]
[@17,25:25='S',<W1>,1:25]
[@18,27:27='3',<W2>,1:27]
[@19,29:29='2',<W2>,1:29]
[@20,30:30=')',<')'>,1:30]
[@21,31:31=')',<')'>,1:31]
[@22,32:32='*',<'*'>,1:32]
[@23,33:33='.',<'.'>,1:33]
[@24,34:34='(',<'('>,1:34]
[@25,35:35='2',<W2>,1:35]
[@26,37:37='T',<W1>,1:37]
[@27,39:39='2',<W2>,1:39]
[@28,41:41='4',<W2>,1:41]
[@29,42:42=')',<')'>,1:42]
[@30,43:42='<EOF>',<EOF>,1:43]
 

Если я заменю узлы в вашем правиле синтаксического анализа на W2s (извините, я понятия не имею, что это должно представлять), я получаю:

введите описание изображения здесь

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

ANTLR работает не так. С ANTLR ваш ввод сначала проходит через лексер (он же Tokenizer) для создания потока токенов. Этот шаг абсолютно ничего не знает о ваших правилах синтаксического анализа. (Вот почему его так часто полезно использовать grun для сброса потока токенов, это дает вам представление о том, на что действуют ваши правила синтаксического анализа (и вы можете видеть в вашем примере, что нет токенов УЗЛА, потому что все они соответствуют W2).

Кроме того, предложение… Может показаться, что запятые являются неотъемлемой частью правильного ввода (если (1C102).((2P23).(3S32))*.(2T24) только они не считаются допустимым вводом. Исходя из этого предположения, я удалил -> skip и добавил их в ваше правило синтаксического анализа (вот почему вы видите их в дереве синтаксического анализа). Результирующая грамматика, которую я использовал, была:

 grammar Semi;

prog: expr ;

expr: expr '*' | expr ('.' | ' ') expr | tuple | LP expr RP;

tuple: LP W2 COMMA W1 COMMA W2 COMMA W2 RP;

LP: '(';
RP: ')';
W1: [PCST0];
W2: [0-9] ;
NODE: [0-9] ;

WS: [ trn]  -> skip; // toss out whitespace
COMMA: ',';
 

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

 grammar Semi;

prog: expr ;

expr: expr '*' | expr ('.' | ' ') expr | tuple | LP expr RP;

tuple: LP nodef=INT COMMA w1=PCST0 COMMA w2=INT COMMA nodet=INT RP;

LP: '(';
RP: ')';
PCST0: [PCST0];
INT: [0-9] ;

COMMA: ',';
WS: [ trn]  -> skip; // toss out whitespace
 

С этим изменением ваш класс контекста кортежа будет иметь средства доступа для w1, w1 и node. узел будет представлять собой массив токенов NBR, как я его определил здесь .

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

1. Спасибо за ваше подробное объяснение, и теперь я чувствую, что это намного понятнее для меня! У меня просто есть еще один вопрос относительно вашей обновленной грамматики, потому что мне нужно получить значение w1 и w2 для разных посетителей, имеет ли смысл, если я добавлю еще один слой в грамматику? Например tuple: LP nodef wc wd nodet RP; nodef: INT; wc: W1; wd: INT; nodet: INT; , где INT — это числа. Поэтому в этом случае я не буду использовать здесь несколько w2.

2. Хороший вопрос. использование меток в ваших правилах синтаксического анализатора — это очень простой способ получить то, что вы ищете, без другого уровня правил (уровни правил легко выходят из-под контроля и затрудняют навигацию. Я отредактировал свой ответ, чтобы показать, как вы это сделаете.