нет жизнеспособной альтернативы при вводе ‘int’ — ANTLR 4 с анализатором Python

#python #antlr

#python #antlr

Вопрос:

У меня возникла проблема при работе с ANTLR4 и разборе с помощью библиотеки Python.

Грамматика:

 grammar SimpleCode;           
program  : 'class' ' ' 'Program' ' ' '{'  field_decl* method_decl*  '}' ;
field_decl : DATA_TYPE variable (',' variable)* ';' ;
method_decl: (DATA_TYPE | 'void')  identifier '(' method_params? ')' block ;
variable : identifier | identifier '[' int_literal ']' ;
method_params : DATA_TYPE identifier (',' DATA_TYPE identifier)* ;
block : '{' var_decl* statement* '}' ;
var_decl : DATA_TYPE identifier (',' identifier)* ';';
statement : location assign_op expr ';' | method_call ';' | 'if' '(' (expr) ')' block ('else' block)? | 'for' identifier '=' (expr) ',' (expr) block | 'return' (expr)? ';' | 'break' ';' | 'continue' ';' | block ;
assign_op : '=' | ' =' | '-=' ;
method_call : method_name '(' method_call_params? ')' | 'callout' (string_literal (',' callout_arg (',' callout_arg)*)?) ;
method_call_params : DATA_TYPE identifier (',' DATA_TYPE identifier)* ;
method_name : identifier ;
location : identifier | identifier '[' expr ']' ;
expr : location | method_call | literal | expr bin_op expr | '-' expr | '!' expr | '(' expr ')' ;
callout_arg : expr | string_literal ;
bin_op : arith_op | rel_op | eq_op | cond_op ;
arith_op : ' ' | '-' | '*' | '/'   '%' ;
rel_op : '<' | '>' | '<=' | '>=' ;
eq_op : '==' | '!=' ;
cond_op : 'amp;amp;' | '||' ;
literal : int_literal | char_literal | bool_literal ;
identifier : ALPHA alpha_num* ;
alpha_num : ALPHA | DIGIT ;
int_literal : decimal_literal | hex_literal ;
decimal_literal : DIGIT DIGIT* ;
hex_literal : '0x' HEX_DIGIT HEX_DIGIT* ;
bool_literal : 'true' | 'false' ;
CHAR: . ;
char_literal : ''' CHAR '''  ;
string_literal : '"' CHAR* '"' ;


DATA_TYPE : INT | BOOL ;

INT : 'int' ;
BOOL : 'boolean' ;
ALPHA : [a-zA-Z] ;
DIGIT : [0-9] ;
HEX_DIGIT : [0-9a-fA-F] ;

White : [ t]  -> skip ;
Newline : ( 'r' 'n'? | 'n' ) -> skip ;
LineComment : '//' ~[rn]* -> skip ;
  

Мой код python для анализа:

 from antlr4 import *
from SimpleCodeLexer import SimpleCodeLexer
from SimpleCodeListener import SimpleCodeListener
from SimpleCodeParser import SimpleCodeParser
import sys

class SimpleCodePrintListener(SimpleCodeListener):
    def enterProgram(self, ctx):
        print(ctx.getText())
        print(ctx.toStringTree())
        # for child in ctx.getChildren():
        #     print(child.getText(), child.getSymbol())

def main():
    input_stream = FileStream('in.in')
    lexer = SimpleCodeLexer(input_stream)
    stream = CommonTokenStream(lexer)
    parser = SimpleCodeParser(stream)
    tree = parser.program()
    printer = SimpleCodePrintListener()
    walker = ParseTreeWalker()
    walker.walk(printer, tree)

if __name__ == '__main__':
    print('Starting parse....')
    main()
  

И in.in файл:

 class Program {
    int main() {
        int v;
        v = 1;
        v = 'c';
        v = true;
        return 0; 
    }
}
  

Я получил эту ошибку после запуска кода python:

строка 2: 7 нет жизнеспособной альтернативы при вводе ‘int’

Результат первой печати:

класс Program {int main() {int v;v = 1;v = ‘c’;v = true; возвращает 0; }}

 ([] class   Program   { int   m a i n ( )   { int   v ; v   =   1 ; v   =   ' c ' ; v   =   true ; return   0 ;   } })
  

Я новичок в ANTLR4, так есть ли какой-либо особый случай для работы с лексерами и токенами, потому что после нескольких часов поиска в Интернете основная проблема заключается в том, что DATA_TYPE используется во многих разных местах грамматики.

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

1. Не могли бы вы подробнее рассказать о том, с какой проблемой вы столкнулись или хотите решить?

2. @Tony Я получил предупреждение «нет жизнеспособной альтернативы при вводе ‘int'», тогда сложно проанализировать дерево всего кода в файле «in.in »

Ответ №1:

При отладке подобных проблем часто помогает распечатать поток токенов, сгенерированный для данного ввода. Вы можете сделать это, запустив grun опцию -tokens или выполнив итерацию stream в своей main функции.

Если вы это сделаете, вы увидите, что main это обозначено как последовательность из четырех CHAR токенов, тогда как ваше identifier правило ожидает ALPHA токены, а не CHAR . Так что это непосредственная проблема, но это не единственная проблема в вашем коде:

  • Первое, что я заметил, когда попробовал ваш код, это то, что я получил ошибки при разрывах строк. Причина, по которой это происходит для меня, а не для вас, заключается (предположительно) в том, что вы используете разрывы строк Windows ( rn ), а я нет. Ваш лексер распознает rn как разрыв строки и пропускает его, но просто n распознается как CHAR .

  • Кроме того, ваша обработка пробелов очень сбивает с толку. Одиночные пробелы являются их собственными токенами. Они должны появляться в определенных местах и не могут появляться нигде больше. Однако несколько последовательных пробелов пропускаются. Так что что-то вроде int main было бы ошибкой, потому что оно не обнаружило бы пробел между int и main . С другой стороны, отступ в строке с одним пробелом будет ошибкой, потому что тогда отступ не будет пропущен.

  • Ваши идентификаторы также шаткие. Идентификаторы могут содержать пробелы (если их больше одного), разрывы строк (если они rn или вы это исправляете, так что n это тоже пропускается) или комментарии. Таким образом, следующий будет единственным допустимым идентификатором (при условии, что вы измените лексер, чтобы буквы распознавались как ALPHA вместо CHAR ):

     hel  lo  //comment
    wor
    ld
      

    С другой стороны maintarget , это не будет допустимым идентификатором, поскольку он содержит ключевое int слово .

  • Аналогично пропущенные токены также могут использоваться внутри ваших целочисленных литералов и строковых литералов. Для строковых литералов это означает, что "a b" это допустимая строка (что нормально), которая содержит только символы a и b (что не нормально), потому что пропускается двойной пробел. С другой стороны " " , это будет недопустимая строка, поскольку распознается как ' ' токен, а не CHAR токен. Кроме того, если вы исправите свои идентификаторы, заставив буквы распознаваться как ALPHA , они больше не будут действительны внутри строк. Также "la//la" будет рассматриваться как незакрытый строковый литерал, потому //la" что будет рассматриваться как комментарий.

Все эти проблемы связаны с тем, как работает лексер, поэтому давайте рассмотрим это:


При превращении потока символов в поток токенов лексер будет обрабатывать ввод в соответствии с правилом "maximal munch": он будет проходить через все правила лексера и проверяет, какое из них соответствует началу текущего ввода. Из тех, которые совпадают, он выберет тот, который выдает самое длинное совпадение. В случае связей он предпочтет тот, который определен первым в грамматике. Если вы используете строковые литералы непосредственно в правилах синтаксического анализатора, они обрабатываются как правила лексера, которые определяются раньше любых других.

Итак, тот факт, что у вас есть CHAR: .; правило, которое предшествует ALPHA , DIGIT и HEX_DIGIT означает, что эти правила никогда не будут совпадать. Все эти правила соответствуют одному символу, поэтому, когда совпадает более одного из них, CHAR будет предпочтительнее, потому что оно стоит на первом месте в грамматике. Если вы перейдете CHAR к концу, буквы теперь будут сопоставляться с помощью ALPHA , десятичные цифры с помощью DIGIT и все остальное с помощью CHAR . Это по-прежнему остается HEX_DIGIT бесполезным (и если вы переместите его на передний план, это будет бесполезно ALPHA и DIGIT бесполезно), и это также означает, что CHAR это больше не делает то, что вы хотите, потому что вы хотите, чтобы цифры и буквы отображались как CHAR s - но только внутри строк.

Реальная проблема здесь заключается в том, что ни одна из этих вещей не должна быть токенами. Они должны быть либо fragment s, либо просто быть встроены непосредственно в правила lexer, которые их используют. Вместо этого ваши токены должны быть чем-либо, внутри чего вы не хотите разрешать / игнорировать пробелы или комментарии. Таким образом, строковые литералы, литералы int и идентификаторы должны быть токенами. Единственным экземпляром, в котором у вас есть несколько правил лексера, которые могут соответствовать одному и тому же вводу, должны быть идентификаторы и ключевые слова (где ключевые слова имеют приоритет над идентификаторами, потому что вы указываете их как строковые литералы в грамматике, но более длинные идентификаторы все равно могут содержать ключевые слова в качестве подстроки из-за правила максимального Мунка).

Вы также должны удалить все виды использования ' ' из своей грамматики и вместо этого всегда пропускать пробелы.