首页 文章

使用ANTLR4在语法中排序词法分析器规则

提问于
浏览
0

我已经阅读了非常有用的ANTLR Mega Tutorial但我仍然坚持如何正确地订购(和/或写)我的词法分析器和解析器规则 .

我希望解析器能够处理这样的事情:

你好“姓名”,你好吗?

在运行时,我将用用户名替换“<< name >>” .

所以我主要解析文本单词(和标点符号等),除了偶尔出现的“<< something >>”标签,我在lexer规则中称之为“func” .

这是我的语法:

doc: item* EOF ;
item: (func | WORD) PUNCT? ;
func: '<<' ID '>>' ;

WS : [ \t\n\r] -> skip ;
fragment LETTER : [a-zA-Z] ;
fragment DIGIT : [0-9] ;
fragment CHAR : (LETTER | DIGIT | SYMB ) ;
WORD : CHAR+ ;
ID: LETTER ( LETTER | DIGIT)* ;
PUNCT : [.,?!] ;
fragment SYMB : ~[a-zA-Z0-9.,?! |{}<>] ;

旁注:我添加了“PUNCT?”在“项目”规则的末尾,因为有可能(例如在我上面给出的例句中)在“func”之后出现逗号 . 但是因为你也可以在“WORD”之后有一个逗号然后我决定将标点符号放在“item”而不是“func”和“WORD”中 .

如果我在上面的句子上运行这个解析器,我会得到一个如下所示的解析树:
Parse tree 1

以红色突出显示的任何内容都是解析错误 .

因此,它不会将双尖括号内的“ID”识别为“ID” . 据推测,这是因为“WORD”在我的词法规则列表中排在第一位 . 但是,我没有说“<< WORD >>”的规则,只有一条说“<< ID >>”的规则,所以我不清楚为什么会这样 .

如果我在语法中交换“ID”和“WORD”的顺序,那么它们现在按以下顺序排列:

ID: LETTER ( LETTER | DIGIT)* ;
WORD : CHAR+ ;

并运行解析器,我得到一个这样的解析树:
Parse tree 2

所以现在正在正确处理“func”和“ID”规则,但没有一个“WORD”被识别 .

我如何克服这个难题?

我想一个选项可能是将“func”规则更改为“<< WORD >>”,并将所有内容视为单词,取消“ID” . 但我想区分文本字和变量标识符(例如,变量标识符中不允许使用特殊字符) .

谢谢你的帮助!

2 回答

  • 1

    来自The Definitive ANTLR 4 Reference

    ANTLR通过将输入字符串与语法中首先指定的规则进行匹配来解决词汇歧义 .

    用你的语法(在Question.g4中)和一个包含的t.text文件

    Hello << name >>, how are you at nine o'clock?
    

    执行

    $ grun Question doc -tokens -diagnostics t.text
    

    [@0,0:4='Hello',<WORD>,1:0]
    [@1,6:7='<<',<'<<'>,1:6]
    [@2,9:12='name',<WORD>,1:9]
    [@3,14:15='>>',<'>>'>,1:14]
    [@4,16:16=',',<PUNCT>,1:16]
    [@5,18:20='how',<WORD>,1:18]
    [@6,22:24='are',<WORD>,1:22]
    [@7,26:28='you',<WORD>,1:26]
    [@8,30:31='at',<WORD>,1:30]
    [@9,33:36='nine',<WORD>,1:33]
    [@10,38:44='o'clock',<WORD>,1:38]
    [@11,45:45='?',<PUNCT>,1:45]
    [@12,47:46='<EOF>',<EOF>,2:0]
    line 1:9 mismatched input 'name' expecting ID
    line 1:14 extraneous input '>>' expecting {<EOF>, '<<', WORD, PUNCT}
    

    现在在 item 规则中将 WORD 更改为 word ,并添加 word 规则:

    item: (func | word) PUNCT? ;
    word: WORD | ID ;
    

    并在WORD之前输入ID:

    ID: LETTER ( LETTER | DIGIT)* ;
    WORD : CHAR+ ;
    

    令牌现在

    [@0,0:4='Hello',<ID>,1:0]
    [@1,6:7='<<',<'<<'>,1:6]
    [@2,9:12='name',<ID>,1:9]
    [@3,14:15='>>',<'>>'>,1:14]
    [@4,16:16=',',<PUNCT>,1:16]
    [@5,18:20='how',<ID>,1:18]
    [@6,22:24='are',<ID>,1:22]
    [@7,26:28='you',<ID>,1:26]
    [@8,30:31='at',<ID>,1:30]
    [@9,33:36='nine',<ID>,1:33]
    [@10,38:44='o'clock',<WORD>,1:38]
    [@11,45:45='?',<PUNCT>,1:45]
    [@12,47:46='<EOF>',<EOF>,2:0]
    

    并且没有更多的错误 . 如-gui图形所示,您现在已将分支标识为 wordfunc .

  • 1

    由于他的评论中已经提到的“500 - 内部服务器错误”,ANTLR将按照语法中定义的顺序匹配词法规则(最顶层的规则将首先匹配),如果某个输入已匹配,则ANTLR将不会尝试不同的是匹配它 .

    在您的情况下, WORDID 规则都可以匹配 abc 之类的输入,但是首先声明 WORD abc 将始终匹配为 WORD ,而不是 ID . 实际上 ID 将永远不会匹配,因为没有有效的输入作为 IDWORD 无法匹配 .

    但是,如果您的唯一目标是替换 <<>> 之间的任何内容,那么最好使用正则表达式 . 但是,如果你仍想使用ANTLR,你应该减少你的语法,只关心基本要素 . 这是为了区分 <<>> 之间的任何输入和输入 . 因此你的语法应该是这样的:

    start: (INTERESTING | UNINTERESTING) ;
    INTERESTING: '<<' .*? '>>' ;
    UNINTERESTING: (~[<])+ | '<' ;
    

    或者你可以完全跳过 UNINTERESTING .

相关问题