定义 Antlr XYZ 文件语法的问题,该语法可以非贪婪地使用 '\n' 终止的字符串

Issue defining an Antlr XYZ File grammar that can consume a '\n' terminated string non greedly

我刚开始使用 Antlr4。作为初始项目,我的任务是为 XYZ files 编写语法,因为它们相对简单。

目前,如果文件中没有评论,它会很好用。

到目前为止,这是我的进步:

grammar XYZFile;

options {
    accessLevel = '';
}

molecule    : nAtomsLine commentLine atom ;

nAtomsLine  : nAtom NEWLINE ;
nAtom       : N_ATOMS ;
atom        :  ( atom3d | atom2d ) NEWLINE? (atom | EOF )? ;
atom3d      : symbol xCoord yCoord zCoord ;
atom2d      : symbol xCoord yCoord ;
xCoord      : FLOAT ;
yCoord      : FLOAT ;
zCoord      : FLOAT ;
symbol      : SYMBOLSTR ;
commentLine : comment NEWLINE ;
comment     : COMMENT? ;
NEWLINE     : '\r'? '\n' ;
SYMBOLSTR   : 'A' ( 'c' | 'g' | 'l' | 'm' | 'r' | 's' | 't' | 'u' )
            | 'B' ( 'a' | 'e' | 'h' | 'i' | 'k' | 'r' )?
            | 'C' ( 'a' | 'd' | 'e' | 'f' | 'l' | 'm' | 'n' | 'o' | 'r' | 's' | 'u' )?
            | 'D' ( 'b' | 's' | 'y' )
            | 'E' ( 'r' | 's' | 'u' )
            | 'F' ( 'e' | 'l' | 'm' | 'r' )?
            | 'G' ( 'a' | 'd' | 'e' )
            | 'H' ( 'e' | 'f' | 'g' | 'o' | 's' )?
            | 'I' ( 'n' | 'r' )?
            | 'K' 'r'?
            | 'L' ( 'a' | 'i' | 'r' | 'u' | 'v' )
            | 'M' ( 'c' | 'g' | 'n' | 'o' | 't' )
            | 'N' ( 'a' | 'b' | 'd' | 'e' | 'h' | 'i' | 'o' | 'p' )?
            | 'O' ( 'g' | 's' )?
            | 'P' ( 'a' | 'b' | 'd' | 'm' | 'o' | 'r' | 't' | 'u' )?
            | 'R' ( 'a' | 'b' | 'e' | 'f' | 'g' | 'h' | 'n' | 'u' )
            | 'S' ( 'b' | 'c' | 'e' | 'g' | 'i' | 'm' | 'n' | 'r' )?
            | 'T' ( 'a' | 'b' | 'c' | 'e' | 'h' | 'i' | 'l' | 'm' | 's' )
            | 'U' | 'V' | 'W' | 'Xe' | 'Y' 'b'?
            | 'Z' ( 'n' | 'r' )
            ;
N_ATOMS     : INT ;

INT         : DIGIT+ ;
FLOAT       : '-'? DIGIT+ '.' DIGIT*
            | '-'? '.' DIGIT+
            ;
WS          : [ \t] -> skip ;
COMMENT     : ~[\n\r].*? ;
fragment
DIGIT       : [0-9] ;

我认为我的问题出在词法分析器的评论部分。它应该消耗一切,直到行尾。目前它故意只消耗一个字符,因为我对它所做的所有修改只会让它变得更糟:

很确定我在沮丧中尝试了很多其他事情,但这些应该足以说明我感到困难的地方。 我知道这个谓词可以匹配其他情况,但我不知道如何避免它。

感谢您的宝贵时间。

ANTLR 解析输入的第一步是将输入的字符流转换为标记流。此过程使用 Lexer 规则(以大写字母开头的规则)。此时,解析器规则无关紧要,解析器规则作用于 Lexer 产生的标记流。

当 Lexer(又名分词器)对您的输入字符进行分词时,它将根据您的所有 Lexer 规则评估您的输入。当超过 1 条规则可以匹配您的输入时,则有两种“决胜局”策略:

  • 匹配最长输入字符流的Lexer规则具有最高优先级。
  • 如果有多个规则匹配相同(最长)的字符序列,则第一个出现的规则“获胜”

在您的语法中,COMMENT 规则 (~[\n\r].*?) 将匹配任何行的完整内容。因此,none 的其他 Lexer 规则确实有机会(当然 NEWLINE 规则除外)。在 COMMENT 规则之前使用其他 Lexer 规则并不重要,因为它们匹配比 COMMENT 规则更短的输入字符流。

看看您提供的 link 中有哪些小“规格”,这将是相当困难的。 (注意:这是大多数语言都有某种“开始评论”标记的东西;通常 //

如果您遵循介绍中的 ANTLR 设置,并定义了 grun 别名,那么通过 grun 输入 运行 始终是一个很好的起点-tokens 标志以查看 Lexer 如何将您的输入流解释为标记流。

您可能会在 COMMENT 规则上使用语义谓词取得一些成功,该规则检查以原子符号或数字开头的行,并且 returns false 以防止 COMMENT 从匹配规则,但文件格式似乎很“宽松”,所以这可能不是很容易管理。

简短的回答是,您的 COMMENT 规则将不得不拒绝不是 XYZ 格式评论的输入,这似乎很含糊。

第二行几乎可以包含任何类型的字符(也包括数字),这使得词法分析器很难区分 digit/number 是注释的一部分还是坐标的一部分(如Mike 已经解释过了)。

为这种文件格式创建语法有点矫枉过正:逐行处理它是更好的选择。但考虑到这更像是一个熟悉 ANTLR 的练习,我会建议您如何去做。

一个解决方案是使词法分析器对上下文敏感一点,以便它“知道”何时处于 3 种模式之一:

  1. 首行模式:可以创建一个整数
  2. 二线模式:任意字符发表评论
  3. 最后模式:包含原子 + 坐标的剩余行

ANTLR 的词法分析器有一个叫做 lexical modes 的东西,您可以在其中 引导 词法分析器采用我上面描述的其中一种模式。但是,为了能够使用词法模式,您必须将词法分析器和解析器语法分开放在它们自己的文件中。

这可能是这样的:

文件:XYZLexer.g4

lexer grammar XYZLexer;

INTEGER
 : [0-9]+
 ;

END_LINE_1
 : [\r\n]+ -> skip, mode(COMMENT_MODE)
 ;

mode COMMENT_MODE;

 COMMENT
  : ~[\r\n]+
  ;

 END_LINE_2
  : [\r\n]+ -> skip, mode(ATOM_MODE)
  ;

mode ATOM_MODE;

 ATOM
  : [a-zA-Z]
  ;

 NUMBER
  : '-'? [0-9]+ '.' [0-9]+
  ;

 SPACES
  : [ \t]+ -> skip
  ;

 LINE_BREAK
  : [\r\n]+
  ;

文件:XYZParser.g4

parser grammar XYZParser;

options {
  tokenVocab=XYZLexer;
}

xyz_file
 : INTEGER COMMENT atom_lines EOF
 ;

atom_lines
 : atom ( LINE_BREAK+ atom )* LINE_BREAK*
 ;

atom
 : ATOM coordinate
 ;

coordinate
 : NUMBER+
 ;

使用从上述语法生成的解析器,输入如下:

2
comment example
  C        0.00000        1.40272        0.00000
  H        0.00000        2.49029        0.00000

会被解析为: