在 ANTLR4 中使用引号或反斜杠处理引号转义的 Lexer 规则?

Lexer rule to handle escape of quote with quote or backslash in ANTLR4?

我正在尝试扩展 的答案,以便在 " 可以使用 " 和 \ 进行转义时工作。 IE。两者

"Rob ""Commander Taco"" Malda is smart."

"Rob \"Commander Taco\" Malda is smart."

均有效且等效。我试过了

StringLiteral : '"' ('""'|'\"'|~["])* '"';

但如果匹配失败

"Entry Flag for Offset check and for \"don't start Chiller Water Pump Request\""

分词器消耗的字符比预期的多,即消耗超过 \""

有人知道如何定义词法分析器规则吗?


更详细一点...

示例:(text.txt)

""
""""
"\" "
"\""
""
grun test tokens -tokens < test.txt
line 5:1 token recognition error at: '"'
[@0,0:1='""',<StringLiteral>,1:0]
[@1,2:2='\n',<'
'>,1:2]
[@2,3:6='""""',<StringLiteral>,2:0]
[@3,7:7='\n',<'
'>,2:4]
[@4,8:12='"\" "',<StringLiteral>,3:0]
[@5,13:13='\n',<'
'>,3:5]
[@6,14:19='"\""\n"',<StringLiteral>,4:0]
[@7,21:20='<EOF>',<EOF>,5:2]
StringListeral 末尾的

\""""" 处理方式不同。

这是该规则的 ATN:

从这张图中不清楚为什么要对它们进行不同的处理。它们似乎是平行结构。


更多研究

测试语法(简化 ATN 的小改动):

grammar test
    ;

start: StringLiteral (WS? StringLiteral)+;

StringLiteral: '"' ( (('\' | '"') '"') | ~["])* '"';
WS:            [ \t\n\r]+;

此语法中 StringLiteral 的 ATN:

好的,让我们通过输入 "\""\n"

遍历这个 ATN
unconsumed input transition
"\""\n" 1 -ε-> 5
"\""\n" 5 -"-> 11
\""\n" 11 -ε-> 9
\""\n" 9 -ε-> 6
\""\n" 6 -\-> 7
""\n" 7 -"-> 10
"\n" 10 -ε-> 13
"\n" 13 -ε-> 11
"\n" 11 -ε-> 12
"\n" 12 -ε-> 14
"\n" 14 -"-> 15
\n" 15 -ε-> 2

我们应该在 \n 之前达到 2 状态 ",这将是所需的行为。

相反,我们看到它继续消耗 \n 和下一个 "

line 2:1 token recognition error at: '"'
[@0,0:5='"\""\n"',<StringLiteral>,1:0]
[@1,7:6='<EOF>',<EOF>,2:2]

为了使其有效,必须有一条从状态 11 到状态 2 的路径消耗一个 \n 和一个 "(我没有看到它)

也许我遗漏了什么,但它对我来说越来越像一个错误。

我无法复制它。给定语法:

grammar T;

parse
 : .*? EOF
 ;

StringLiteral
 : '"' ( '""' | '\"' | ~["] )* '"'
 ;

Other
 : . -> skip
 ;

以下代码:

String source =
    "\"Rob \"\"Commander Taco\"\" Malda is smart.\"\n" +
    "\"Rob \\"Commander Taco\\" Malda is smart.\"\n" +
    "\"Entry Flag for Offset check and for \\"don't start Chiller Water Pump Request\\"\"\n";

TLexer lexer = new TLexer(CharStreams.fromString(source));
CommonTokenStream stream = new CommonTokenStream(lexer);

stream.fill();

for (Token t : stream.getTokens()) {
    System.out.printf("%-20s '%s'\n",
        TLexer.VOCABULARY.getSymbolicName(t.getType()),
        t.getText().replace("\n", "\n"));
}

产生以下输出:

StringLiteral        '"Rob ""Commander Taco"" Malda is smart."'
StringLiteral        '"Rob \"Commander Taco\" Malda is smart."'
StringLiteral        '"Entry Flag for Offset check and for \"don't start Chiller Water Pump Request\""'

使用 ANTLR 4.9.3 和 4.10.1 测试:两者产生相同的输出。

问题是正确处理 \

Bart 找到了我错过的通过 ATN 的路径,并允许它匹配额外的 \n"\ 作为 ~["] 匹配,然后返回并匹配 " 以终止字符串。

我们可以在“除 " 替代方案 (~["\]) 之外的所有内容中禁止 \,但是我们必须允许 stand-alone \是可以接受的。我们想添加一个替代方案,允许 \ 后跟 " 以外的任何内容。你会认为 '\' ~["] 会那样做,你会对,在某种程度上,但它也消耗了 \ 之后的字符,如果你想要像 "test \" string" 这样的字符串,这是一个问题,因为它消耗了你无法匹配的第二个 \ \" 替代方案。你正在寻找的是前瞻性的(即如果 \ 后面没有 ",则使用它,但不要使用以下字符)。但是 ANTLR词法分析器规则不允许前瞻 (ANTLR lexer can't lookahead at all)。

您会注意到,大多数允许 \" 作为字符串中的转义序列的语法也需要对空 \ 进行转义 (\),并且经常将其他 \(其他字符)序列作为“其他字符”)。

如果转义 \ 字符是可以接受的,则规则可以简化为:

StringLiteral: '"' ('\' . | '""' | ~["\])* '"';

"Flag for \"Chiller Water\"" 不会正确解析,但 "Flag for \\"Chiller Water\\"" 会。没有前瞻性,我看不到 Lex 第一个版本的方法。


此外,请注意,如果您不转义 \,那么您对 ​​\"" 的解释不明确。是 \" 后跟 " 来终止字符串,还是 \ 后跟 "" 允许字符串继续? ANTLR 将采用消耗最多输入的任何一种解释,因此我们使用第二种解释看到它并拉入字符,直到找到 "