#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 и идентификаторы должны быть токенами. Единственным экземпляром, в котором у вас есть несколько правил лексера, которые могут соответствовать одному и тому же вводу, должны быть идентификаторы и ключевые слова (где ключевые слова имеют приоритет над идентификаторами, потому что вы указываете их как строковые литералы в грамматике, но более длинные идентификаторы все равно могут содержать ключевые слова в качестве подстроки из-за правила максимального Мунка).
Вы также должны удалить все виды использования ' '
из своей грамматики и вместо этого всегда пропускать пробелы.