ANTLR4 词法分析器规则在 perl 语法上产生错误或冲突

ANTLR4 lexer rule creates errors or conflicts on perl grammar

我的 PERL 语法有问题,这里是我的语法的相关部分:

element
    : element (ASTERISK_CHAR | SLASH_CHAR | PERCENT_CHAR) element
    | word
    ;

SLASH_CHAR:                 '/';

REGEX_STRING
    : '/' (~('/' | '\r' | '\n') | NEW_LINE)* '/'
    ;

fragment NEW_LINE
    : '\r'? '\n'
    ;

如果规则 REGEX_STRING 未被注释,则以下 perl 不会解析:

$b = 1/2;
$c = 1/2;
<2021/08/20-19:24:37> <ERROR> [parsing.AntlrErrorLogger] - Unit 1: <unknown>:2:6: extraneous input '/2;\r\n$c = 1/' expecting {<EOF>, '=', '**=', '+=', '-=', '.=', '*=', '/=', '%=', CROSS_EQUAL, '&=', '|=', '^=', '&.=', '|.=', '^.=', '<<=', '>>=', '&&=', '||=', '//=', '==', '>=', '<=', '<=>', '<>', '!=', '>', '<', '~~', '++', '--', '**', '.', '+', '-', '*', '/', '%', '=~', '!~', '&&', '||', '//', '&', '&.', '|', '|.', '^', '^.', '<<', '>>', '..', '...', '?', ';', X_KEYWORD, AND, CMP, EQ, FOR, FOREACH, GE, GT, IF, ISA, LE, LT, OR, NE, UNLESS, UNTIL, WHEN, WHILE, XOR, UNSIGNED_INTEGER}

请注意,在何处使用词法分析器规则 REGEX_STRING 并不重要,即使它在解析器规则中的任何地方都不存在,只是在这里会导致分析失败(因此问题出在词法分析器方面) . 如果我删除词法分析器规则 REGEX_STRING,那么它会被解析得很好,但我无法解析:

$dateCalc =~ /^([0-9]{4})([0-9]{2})([0-9]{2})/

此外,我注意到这个 perl 解析,因此第一个和第二个 '/' 之间似乎存在某种交互。

$b = 12;          # Removed the / between 1 and 2
$c = 1/2;         # Removing the / here would work as well.

我似乎找不到如何编写我的正则表达式词法分析器规则来不让某些事情失败。 我错过了什么?我怎样才能很好地解析这两个表达式?

这里的基本问题是 ANTLR4 与许多其他解析框架一样,执行独立于语法的词法分析;无论解析器可以接受哪些标记,都会生成相同的标记。因此,词法分析器必须决定给定的 / 是除法运算符还是正则表达式的开头,这个决定实际上只能使用句法信息来做出。 (有些解析框架没有此限制,因此可用于实现无扫描器解析器。这些包括基于 PEG 的解析器和 GLR/GLR 解析器。)

ANTLR4 example directory 中有一个解决这种词法歧义的例子,它也出现在解析 ECMAScript 中。 (这是一个 github 永久链接,以便下面引用的行号继续有效。)

基本策略是决定一个/是否可以根据紧接在前的标记开始一个正则表达式。这在 ECMAScript 中有效,因为运算符(例如 //=)可以出现的语法上下文与操作数可以出现的上下文不相交。这可能不会直接转化为 Perl 解析器,但它可能有助于显示可能性。

Line 780-782:正则表达式令牌本身受语义保护保护:

RegularExpressionLiteral
 : {isRegexPossible()}? '/' RegularExpressionBody '/' RegularExpressionFlags
 ;

Lines 154-182: guard 函数本身很简单,但显然需要一定量的语法分析才能生成正确的测试。 (注意:令牌列表已被缩写;完整列表见原始文件):

private boolean isRegexPossible() {
        if (this.lastToken == null) {
            return true;
        }

        switch (this.lastToken.getType()) {
            case Identifier:
            case NullLiteral:
...
                // After any of the tokens above, no regex literal can follow.
                return false;
            default:
                // In all other cases, a regex literal _is_ possible.
                return true;
        }
    }
}

Lines 127-147 为了使其工作,扫描器必须在成员变量 last_token 中保留先前的标记。 (已删除 space 的评论):

    @Override
    public Token nextToken() {
        Token next = super.nextToken();
        if (next.getChannel() == Token.DEFAULT_CHANNEL) {
            this.lastToken = next;
        }
        return next;
    }