定义 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] ;
我认为我的问题出在词法分析器的评论部分。它应该消耗一切,直到行尾。目前它故意只消耗一个字符,因为我对它所做的所有修改只会让它变得更糟:
.*? NEWLINE
~[\n\r]*?
~[\n\r]*? NEWLINE
~[\n\r] .*? NEWLINE
很确定我在沮丧中尝试了很多其他事情,但这些应该足以说明我感到困难的地方。
我知道这个谓词可以匹配其他情况,但我不知道如何避免它。
感谢您的宝贵时间。
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 种模式之一:
- 首行模式:可以创建一个整数
- 二线模式:任意字符发表评论
- 最后模式:包含原子 + 坐标的剩余行
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
会被解析为:
我刚开始使用 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] ;
我认为我的问题出在词法分析器的评论部分。它应该消耗一切,直到行尾。目前它故意只消耗一个字符,因为我对它所做的所有修改只会让它变得更糟:
.*? NEWLINE
~[\n\r]*?
~[\n\r]*? NEWLINE
~[\n\r] .*? NEWLINE
很确定我在沮丧中尝试了很多其他事情,但这些应该足以说明我感到困难的地方。 我知道这个谓词可以匹配其他情况,但我不知道如何避免它。
感谢您的宝贵时间。
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 种模式之一:
- 首行模式:可以创建一个整数
- 二线模式:任意字符发表评论
- 最后模式:包含原子 + 坐标的剩余行
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
会被解析为: