ANTLR解析规则匹配:以任意顺序匹配x和/或y和/或z

3
使用ANTLR,是否有一种方法可以编写解析器规则,以便它可以表达:x和/或y和/或z以任何顺序而无需编写Java。例如,它应该匹配:"x y"、"y z"和"x y z",但不应匹配"x x y"。我能想到的最好方法是下面的规则,但我需要在树遍历器中检查"x x y"。
rule: ( x | y | z )* ;
2个回答

3

虽然你可以做一些类似于:

rule: x
    | y
    | z
    | x y
    | y x
    | x z
    | z x
    | y z
    | z y
    | x y z
    | x z y
    | y x z
    | y z x
    | z x y
    | z y x;

或者(稍微不那么荒谬):
rule: x? y? z?
    | x? z? y?
    | y? x? z?
    | y? z? x?
    | z? x? y?
    | z? y? x?
    ;

我怀疑你的示例比实际应用程序要简单得多,这种方法会变得非常乏味(它已经变得荒谬)。
你也可以尝试使用语义断言来处理问题,但这将使你的语法与特定的目标语言绑定(这也会使你的语法变得更加复杂)。
总的来说,我发现ANTLR用户(以及一般的解析器编写者)经常过于努力地将“所有规则”编码到语法中。
这似乎很好,但它会导致语法非常复杂,并且错误消息不够理想(因为它们来自解析器(ANTLR)本身)。
我认为最好保留像你的规则一样的规则,这将创建一个准确表示解释输入的正确方式的解析树。然后,你可以将此类规则视为语义关注点(而不是语法关注点(解析器的领域)。
这意味着你可以编写一个验证监听器针对你的解析树运行,并检查同一子规则是否使用超过一次。如果遇到这种情况,则可以制作出非常具体的错误消息,这对最终用户更有用。

谢谢Mike。可惜你是对的,有三个情况我想使用“技巧”,分别涉及3、4和7个变量,所以组合方法并不完全适用!我最初计划在解析器之外处理问题,但这也变得非常困难 - 目前我有5个监听器,所以我正在尽可能地将更多内容推回到解析器中。 - Keith Whittingham
也许现在有人会有一个我没有想到的想法,但我建议拥有多个监听器并不是那么糟糕,而且更符合“ANTLR方式”。我总是发现我需要一个验证监听器来进行语义错误检查。如果现有的监听器之一正在进行验证,那么我会添加到其中。根据您的用例,如果它有助于代码清晰度并分离关注点,我不知道5个监听器是否是一个“坏事”。(待续) - Mike Cargal
我发现有时我会故意忽略语法中的某些规则,只要语法能给我正确解释输入的ParseTree。这样我就可以提供比只依靠ANTLR生成的语法规则更好的错误信息了。(另一个诀窍是针对已知的问题构造实际规则,这样你就可以识别它们并提供更有意义的错误消息。) - Mike Cargal
我已经添加了下面的答案,这是我能想到的最好的,看起来比我想象的要好。我可以在不同的规则中重复使用布尔值,并且我不打算在Java之外使用它... - Keith Whittingham

1

我能想到的最好的是...

grammar Sandbox;

@members {
    boolean a, b, c;
}

start: ( 'test' test )+ EOF ;

test:
    {a=b=c=true;}   // Reset
    (   {a}? a {a=false;}
    |   {b}? b {b=false;}
    |   {c}? c {c=false;}
    )* ;

a: 'a';
b: 'b';
c: 'c';

WS : [ \t\r\n]+ -> skip ;

而测试驱动程序...

package sandbox;

import org.antlr.v4.runtime.*;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    private Main() {
        System.out.println("Should be OK...");
        test("test a b c test c test c b a test c");
        System.out.println("Should fail...");
        test("test c a a");
    }

    private void test(String toTest) {
        final CharStream cs = CharStreams.fromString(toTest);
        final SandboxLexer lexer = new SandboxLexer(cs);
        final CommonTokenStream tokens = new CommonTokenStream(lexer);
        final SandboxParser parser = new SandboxParser(tokens);
        parser.start();
    }
}

这就是我所提到的语义谓词解决方案(我没有费心去写 :))。是的,它并不像我想象的那么丑。 - Mike Cargal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接