输入'int'没有可行的选择-使用Python Parser的ANTLR 4

时间:2019-04-09 08:35:14

标签: 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 : '&&' | '||' ;
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 : '//' ~[\r\n]* -> 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'

第一次打印的结果是:

类程序{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的新手,所以有什么特殊情况要处理词法分析器和标记,因为经过Internet数小时的搜索后,主要问题是DATA_TYPE在语法的许多不同地方都使用过。

1 个答案:

答案 0 :(得分:1)

在调试此类问题时,通常有助于打印为给定输入生成的令牌流。您可以通过使用选项grun运行-tokens或通过在stream函数中遍历main来实现。

如果执行此操作,则会看到main被标记为四个CHAR标记的序列,而您的identifier规则需要ALPHA标记,而不是{ {1}}。所以这是当前的问题,但这不是代码中的唯一问题:

  • 尝试代码时,我注意到的第一件事是换行符出现错误。这种情况发生在我而不是你身上的原因(大概)是因为您使用的是Windows换行符(CHAR),而我没有。您的词法分析器将\r\n识别为换行符并跳过它,但是只有\r\n被识别为\n

  • 此外,您对空间的处理非常令人困惑。单个空格是它们自己的标记。它们必须出现在某些地方,并且不能出现在其他任何地方。但是,将跳过多个连续的空格。因此,类似CHAR之类的东西将是错误的,因为它不会检测到int mainint之间的空格。另一方面,用一行空格缩进一行将是一个错误,因为这样缩进不会被跳过。

  • 您的标识符也很奇怪。标识符可以包含空格(只要不止一个,就可以),换行符(只要它们是main或您对其进行修复,从而也可以跳过\r\n)或注释。因此,以下内容将是一个有效的标识符(假设您更改词法分析器,以便字母被识别为\n而不是ALPHA):

    CHAR

    另一方面,hel lo //comment wor ld 不是有效的标识符,因为它包含关键字maintarget

  • 类似地,也可以在整数文字和字符串文字中使用跳过的标记。对于字符串文字,这意味着int是有效的字符串(可以),它只包含字符"a b"a(这不是很好),因为会跳过双倍空格。另一方面,b将是无效的字符串,因为" "被识别为令牌,而不是' '令牌。另外,如果通过将字母识别为CHAR来固定标识符,则它们在字符串内将不再有效。另外,ALPHA会被视为未封闭的字符串文字,因为"la//la"会被视为注释。

所有这些问题都与词法分析器的工作方式有关,所以让我们来看一下:


当将字符流转换为令牌流时,词法分析器将根据“最大字符数”规则处理输入:它将通过所有词法分析器规则,并检查在当前输入的开头是否匹配哪个规则。在匹配的那些中,它将选择产生最长匹配的那个。如果是平局,它将更喜欢在语法中首先定义的平局。如果直接在解析器规则中使用字符串文字,则它们将像在其他任何文字规则之前定义的lexer规则一样对待。

因此,您拥有//la"CHAR: .;ALPHA之前的DIGIT规则,这意味着这些规则将永远不会被匹配。所有这些规则都匹配一个字符,因此,当多个规则匹配时,HEX_DIGIT将是首选,因为它在语法中排在首位。如果将CHAR移到末尾,则字母现在将由CHAR匹配,十进制数字将由ALPHA匹配,而其他所有字符将由DIGIT匹配。这仍然使CHAR毫无用处(如果将其移到最前面,将使HEX_DIGITALPHA变得无用),这也意味着DIGIT不再执行您的操作想要,因为您想要数字和字母被视为CHAR,但只能在字符串内部。

这里的真正问题是这些东西都不应该是令牌。它们应该是CHAR或只是直接内联到使用它们的词法分析器规则中。相反,您的令牌应该是您不想在其中允许/忽略空格或注释的任何内容。因此,字符串文字,int文字和标识符都应为标记。唯一具有多个可匹配同一输入的词法分析器规则的实例应该是标识符和关键字(关键字优先于标识符,因为您在语法中将它们指定为字符串文字,但是较长的标识符仍可能包含关键字作为子字符串,因为最大的munch规则。

您还应该从语法中删除对fragment的所有使用,而始终跳过空格。