ANTLR v4:如何在某个令牌之后将任意修剪过的字符串捕获到行/文件的末尾?

时间:2016-10-20 15:55:21

标签: antlr4

出于好奇心的缘故,我正在学习ANTLR,特别是4,我正在尝试创建一个简单的语法。我选择NES(Nintentdo娱乐系统)Game Genie文件是第一次尝试。让我们说,这里有一个示例游戏精灵文件,用于在互联网上找到的侏罗纪公园:

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规则来匹配arbirtrary文本,直到包含空格的行(或文件)结束,但是修剪在左边和右边。如果我将+添加到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

2 个答案:

答案 0 :(得分: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 lexer调试器,您可以很好地突出显示所有令牌类型和令牌模式。

这是解析器语法(文件名为NesGameGenieParser.g4):

parser grammar NesGameGenieParser;

options {
  tokenVocab=NesGameGenieLexer;
}

file: line+;

line : code comment
     | code PLUS code comment;

code: CODE;

comment: COMMENT;

在这里,我假设CODE只是PLUS之前的字符集,但显然很容易改变:)

答案 1 :(得分:0)

花费整个不眠之夜,没有多少时间睡觉,我似乎设法写了词法分析器和解析器语法。无需多解释,请参阅源代码中的注释,简而言之:

词霸:

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提供模式的想法。