ANTLR v4:如何在某个标记后捕获任意修剪的字符串到 line/file 的末尾?
ANTLR v4: How do I capture an arbitrary trimmed string to the end of line/file after a certain token?
出于好奇,我正在学习 ANTLR,尤其是 4,我正在尝试创建一个简单的语法。我第一次尝试选择了 NES(Nintentdo Entertainment System)Game Genie 文件。比方说,这是在 Internet 某处找到的侏罗纪公园的示例游戏精灵文件:
GZUXXKVS Infinite ammo on pick-up
PAVPAGZE More bullets picked up from small dinosaurs
PAVPAGZA Fewer bullets picked up from small dinosaurs
GZEULOVK Infinite lives--1st 2 Levels only
ATVGZOSA Immune to most attacks
VEXASASA + VEUAXASA 3-ball bolas picked up
NEXASASA + NEUAXASA Explosive multi-shots
这是我正在研究的语法。
grammar NesGameGenie;
all: lines EOF;
lines: (anyLine? EOL+)* anyLine?;
anyLine: codeLine;
codeLine: code;
code: CODE (PLUS? CODE)*;
CODE: SHORT_CODE | LONG_CODE;
fragment SHORT_CODE: FF FF FF FF FF FF;
fragment LONG_CODE: FF FF FF FF FF FF FF FF;
fragment FF: [APZLGITYEOXUKSVN];
COMMENT: COMMENT_START NOEOL -> skip;
COMMENT_START: [#;];
EOL: '\r'? '\n';
PLUS: '+';
WS: [ \t]+ -> skip;
fragment NOEOL: ~[\r\n]*;
好吧,它短得可笑而且简单,但我仍然可以看到两个问题:
- 金手指描述导致识别错误,如
line 1:16 token recognition error at: 'In'
,因为没有提供语法描述规则。
- 在描述中添加
#
符号可能会导致忽略行尾的其余部分。至少,AAAAAA Player #1 ammo
只报告 Player
并且 #1 ammo
不幸被解析为评论,但我认为一旦引入描述规则就可以修复它。
我之前尝试添加描述规则导致了很多各种错误,我找到了一个没有错误但仍然不是很好的解决方案:
...
codeLine: code description?;
...
description: PRINTABLE+;
...
PRINTABLE: [\u0020-\uFFFE];
...
不幸的是,每个字符都被解析为单个 PRINTABLE
,而我正在寻找的是一个 description
规则来匹配任意文本直到行(或文件)的末尾,包括空格,但左右修剪。如果我在 PRINTABLE
的末尾添加 +
,则整个文档被视为无效。我想 PRINTABLE
可能会以某种方式安全地内联到 description
规则,但是 description: ('\u0020' .. '\uFFFE')+;
捕获的方式更多。
应该如何声明 description
规则,让它在代码之后捕捉到行尾的所有字符,但只去除左右两边的空格 ([ \t]
)?简单地说,我会有一个语法可以解析成类似的东西(包括 #
字符不将其解析为评论):
code=..., description="Infinite ammo on pick-up"
code=..., description="More bullets picked up from small dinosaurs"
code=..., description="Fewer bullets picked up from small dinosaurs"
code=..., description="Infinite lives--1st 2 Levels only"
code=..., description="Immune to most attacks"
code=..., description="3-ball bolas picked up"
code=..., description="Explosive multi-shots"
再注意一点,我正在使用:
- IntelliJ IDEA 2016.1.1 CE
- IJ插件:ANTLR v4语法插件1.8.1
- IJ 插件:ANTLRWorks 1.3.1
实际上很简单,只需使用词法分析器模式即可。一旦你命中某些标记,改变模式。
这是词法分析器语法,解析器很容易基于它(文件名是 NesGameGenieLexer.g4
):
lexer grammar NesGameGenieLexer;
CODE: [A-Z]+;
WS : [ ]+ -> skip, mode(COMMENT_MODE);
mode COMMENT_MODE;
PLUS: '+' (' ')* -> mode(DEFAULT_MODE);
fragment ANY_CHAR: [a-zA-Z_/0-9=.\-\ ];
COMMENT: ANY_CHAR+;
NEWLINE: [\r\n] -> skip, mode(DEFAULT_MODE);
我假设 +
不能在评论中。如果您使用 ANTLRWorks 词法分析器调试器,您可以看到所有标记类型和标记模式都很好地突出显示。
这是解析器语法(文件名为 NesGameGenieParser.g4
):
parser grammar NesGameGenieParser;
options {
tokenVocab=NesGameGenieLexer;
}
file: line+;
line : code comment
| code PLUS code comment;
code: CODE;
comment: COMMENT;
这里我假设 CODE
只是 PLUS
之前的一组字符,但显然这很容易改变 :)
一夜没睡,睡的时间少了很多,我似乎已经成功地写出了词法分析器和语法分析器。不用多解释,看源码中的注释,简而言之:
词法分析器:
lexer grammar NesGameGenieLexer;
COMMENT: [#;] ~[\r\n]+ [\r\n]+ -> skip;
CODE: (SHORT_CODE | LONG_CODE) -> mode(CODE_FOUND_MODE);
fragment SHORT_CODE: FF FF FF FF FF FF;
fragment LONG_CODE: FF FF FF FF FF FF FF FF;
fragment FF: [APZLGITYEOXUKSVN];
WS: [\t ]+ -> skip;
mode CODE_FOUND_MODE;
PLUS: [\t ]* '+' [\t ]* -> mode(DEFAULT_MODE);
// Skip inline whitespaces and switch to the description detection mode.
DESCRIPTION_LEFT_DELIMITER: [\t ]+ -> skip, mode(DESCRIPTION_FOUND_MODE);
NEW_LINE_IN_CODE_FOUND_MODE: [\r\n]+ -> skip, mode(DEFAULT_MODE);
mode DESCRIPTION_FOUND_MODE;
// Greedily grab all non-CRLF characters and ignore trailing spaces - this is a trimming operation equivalent, I guess.
DESCRIPTION: ~[\r\n]*~[\r\n\t ]+;
// But then terminate the line and switch to the code detection mode.
// This operation is probably required because the `DESCRIPTION: ... -> mode(CODE_FOUND_MODE)` seems not working
NEW_LINE_IN_DESCRIPTION_FOUND_MODE: [\r\n]+ -> skip, mode(DEFAULT_MODE);
解析器:
parser grammar NesGameGenieParser;
options {
tokenVocab = NesGameGenieLexer;
}
file
: line+
;
line
: code description?
| code (PLUS code)* description?
;
code
: CODE
;
description
: DESCRIPTION
;
它看起来比我想象的要复杂得多,但它似乎完全按照我想要的方式工作。另外,我不确定上面的语法是否真的写得很好和惯用。感谢@cantSleepNow 提供模式想法。
出于好奇,我正在学习 ANTLR,尤其是 4,我正在尝试创建一个简单的语法。我第一次尝试选择了 NES(Nintentdo Entertainment System)Game Genie 文件。比方说,这是在 Internet 某处找到的侏罗纪公园的示例游戏精灵文件:
GZUXXKVS Infinite ammo on pick-up
PAVPAGZE More bullets picked up from small dinosaurs
PAVPAGZA Fewer bullets picked up from small dinosaurs
GZEULOVK Infinite lives--1st 2 Levels only
ATVGZOSA Immune to most attacks
VEXASASA + VEUAXASA 3-ball bolas picked up
NEXASASA + NEUAXASA Explosive multi-shots
这是我正在研究的语法。
grammar NesGameGenie;
all: lines EOF;
lines: (anyLine? EOL+)* anyLine?;
anyLine: codeLine;
codeLine: code;
code: CODE (PLUS? CODE)*;
CODE: SHORT_CODE | LONG_CODE;
fragment SHORT_CODE: FF FF FF FF FF FF;
fragment LONG_CODE: FF FF FF FF FF FF FF FF;
fragment FF: [APZLGITYEOXUKSVN];
COMMENT: COMMENT_START NOEOL -> skip;
COMMENT_START: [#;];
EOL: '\r'? '\n';
PLUS: '+';
WS: [ \t]+ -> skip;
fragment NOEOL: ~[\r\n]*;
好吧,它短得可笑而且简单,但我仍然可以看到两个问题:
- 金手指描述导致识别错误,如
line 1:16 token recognition error at: 'In'
,因为没有提供语法描述规则。 - 在描述中添加
#
符号可能会导致忽略行尾的其余部分。至少,AAAAAA Player #1 ammo
只报告Player
并且#1 ammo
不幸被解析为评论,但我认为一旦引入描述规则就可以修复它。
我之前尝试添加描述规则导致了很多各种错误,我找到了一个没有错误但仍然不是很好的解决方案:
...
codeLine: code description?;
...
description: PRINTABLE+;
...
PRINTABLE: [\u0020-\uFFFE];
...
不幸的是,每个字符都被解析为单个 PRINTABLE
,而我正在寻找的是一个 description
规则来匹配任意文本直到行(或文件)的末尾,包括空格,但左右修剪。如果我在 PRINTABLE
的末尾添加 +
,则整个文档被视为无效。我想 PRINTABLE
可能会以某种方式安全地内联到 description
规则,但是 description: ('\u0020' .. '\uFFFE')+;
捕获的方式更多。
应该如何声明 description
规则,让它在代码之后捕捉到行尾的所有字符,但只去除左右两边的空格 ([ \t]
)?简单地说,我会有一个语法可以解析成类似的东西(包括 #
字符不将其解析为评论):
code=..., description="Infinite ammo on pick-up"
code=..., description="More bullets picked up from small dinosaurs"
code=..., description="Fewer bullets picked up from small dinosaurs"
code=..., description="Infinite lives--1st 2 Levels only"
code=..., description="Immune to most attacks"
code=..., description="3-ball bolas picked up"
code=..., description="Explosive multi-shots"
再注意一点,我正在使用:
- IntelliJ IDEA 2016.1.1 CE
- IJ插件:ANTLR v4语法插件1.8.1
- IJ 插件:ANTLRWorks 1.3.1
实际上很简单,只需使用词法分析器模式即可。一旦你命中某些标记,改变模式。
这是词法分析器语法,解析器很容易基于它(文件名是 NesGameGenieLexer.g4
):
lexer grammar NesGameGenieLexer;
CODE: [A-Z]+;
WS : [ ]+ -> skip, mode(COMMENT_MODE);
mode COMMENT_MODE;
PLUS: '+' (' ')* -> mode(DEFAULT_MODE);
fragment ANY_CHAR: [a-zA-Z_/0-9=.\-\ ];
COMMENT: ANY_CHAR+;
NEWLINE: [\r\n] -> skip, mode(DEFAULT_MODE);
我假设 +
不能在评论中。如果您使用 ANTLRWorks 词法分析器调试器,您可以看到所有标记类型和标记模式都很好地突出显示。
这是解析器语法(文件名为 NesGameGenieParser.g4
):
parser grammar NesGameGenieParser;
options {
tokenVocab=NesGameGenieLexer;
}
file: line+;
line : code comment
| code PLUS code comment;
code: CODE;
comment: COMMENT;
这里我假设 CODE
只是 PLUS
之前的一组字符,但显然这很容易改变 :)
一夜没睡,睡的时间少了很多,我似乎已经成功地写出了词法分析器和语法分析器。不用多解释,看源码中的注释,简而言之:
词法分析器:
lexer grammar NesGameGenieLexer;
COMMENT: [#;] ~[\r\n]+ [\r\n]+ -> skip;
CODE: (SHORT_CODE | LONG_CODE) -> mode(CODE_FOUND_MODE);
fragment SHORT_CODE: FF FF FF FF FF FF;
fragment LONG_CODE: FF FF FF FF FF FF FF FF;
fragment FF: [APZLGITYEOXUKSVN];
WS: [\t ]+ -> skip;
mode CODE_FOUND_MODE;
PLUS: [\t ]* '+' [\t ]* -> mode(DEFAULT_MODE);
// Skip inline whitespaces and switch to the description detection mode.
DESCRIPTION_LEFT_DELIMITER: [\t ]+ -> skip, mode(DESCRIPTION_FOUND_MODE);
NEW_LINE_IN_CODE_FOUND_MODE: [\r\n]+ -> skip, mode(DEFAULT_MODE);
mode DESCRIPTION_FOUND_MODE;
// Greedily grab all non-CRLF characters and ignore trailing spaces - this is a trimming operation equivalent, I guess.
DESCRIPTION: ~[\r\n]*~[\r\n\t ]+;
// But then terminate the line and switch to the code detection mode.
// This operation is probably required because the `DESCRIPTION: ... -> mode(CODE_FOUND_MODE)` seems not working
NEW_LINE_IN_DESCRIPTION_FOUND_MODE: [\r\n]+ -> skip, mode(DEFAULT_MODE);
解析器:
parser grammar NesGameGenieParser;
options {
tokenVocab = NesGameGenieLexer;
}
file
: line+
;
line
: code description?
| code (PLUS code)* description?
;
code
: CODE
;
description
: DESCRIPTION
;
它看起来比我想象的要复杂得多,但它似乎完全按照我想要的方式工作。另外,我不确定上面的语法是否真的写得很好和惯用。感谢@cantSleepNow 提供模式想法。