【问题标题】:Two-level grammar with ANTLR 3ANTLR 3 的两级语法
【发布时间】:2016-07-27 15:32:20
【问题描述】:

我有一个语法(如果你稍微简化一下的话)看起来像这样:

options
{
    backtrack=true;
}

// parser
text: (TEXT)+;

and_level2_thing: text | '(' and_thing ')';

and_thing: and_level2_thing (OP_AND and_level2_thing)*;

and_expression: and_thing (OP_AND and_thing)*;

parse_starts_here: and_expression EOF;

// lexer
OP_AND : 'AND';
TEXT : ( '"' (~'"')* '"' );

它有两种类型的表达式组顶级 (and_thing) 和内部级别 (and_level2_thing),它们适用不同的规则,但两个级别都必须支持 AND,例如TOP_TYPE_EXPRESSION AND TOP_TYPE_EXPRESSIONTOP_TYPE_EXPRESSION AND (INNER_TYPE_EXPRESSION AND INNER_TYPE_EXPRESSION).

当我有一个表单的值时:

(TOP_TYPE_EXPRESSION AND (TOP_TYPE_EXPRESSION AND (TOP_TYPE_EXPRESSION AND (TOP_TYPE_EXPRESSION))))

时间开始在嵌套级别上呈指数增长,大概是因为 AND 不明确。这个表达式立即计算:

TOP_TYPE_EXPRESSION AND TOP_TYPE_EXPRESSION AND TOP_TYPE_EXPRESSION AND TOP_TYPE_EXPRESSION

如果你说这不是一种设计良好的语言 - 我完全同意,但这就是我现在所拥有的 :)。任何想法如何避免这个问题?

【问题讨论】:

    标签: compiler-construction antlr antlr3


    【解决方案1】:

    你的语法有歧义:

    "a" AND "b"
    

    可以匹配为

    parse_starts_here
      and_expression
        and_thing
          and_level2_thing
            text
          OP_AND
          and_level2_thing
            text
    

    或作为

    parse_starts_here
      and_expression
        and_thing
          and_level2_thing
            text
        OP_AND
        and_thing
          and_level2_thing
            text
    

    通常,ANTLR 会警告您这种歧义,但通过声明 backtrack = true,您实际上是告诉 ANTLR 尝试所有替代方案并首先使用匹配的。

    在明确的文法上,ANTLR 以线性时间运行。使用回溯会导致潜在的指数时间。 memoize=true 用于以使用更多内存为代价将时间缩短回线性。

    我建议删除 backtrack=true 选项。然后 ANTLR 会告诉你语法在哪里不明确。您可以消除歧义,或者如果不可能,仅在需要的地方使用语法谓词来选择一个可能的匹配项而不是另一个。如果您最终使用语法谓词,memoize=true 仍然会有所帮助。


    编辑 - 至于为什么即使两个选项都匹配也会回溯:

    它不会回溯,但时间仍然是指数级的。

    问题是 ANTLR 不知道它可以匹配第一个替代方案,直到它实际尝试匹配它(因为你没有给它任何提示)。因此它将首先尝试匹配规则,如果成功,它将实际匹配它并执行所有相关操作(memoize 选项通过记住给定输入位置成功的特定规则而不重复整个匹配过程来避免这种情况)。

    例子:

    "a" AND ( "b" AND "c" )
    

    为了匹配这个,ANTLR 必须:

    • 匹配"a"
    • 判断AND是否可以使用内部规则匹配
      • 为此,它会尝试匹配内部规则
      • AND 匹配,( 表示转到 and_thing
      • 要匹配and_thing,它必须:
        • 匹配("b"
        • 判断AND是否可以使用内部规则匹配
          • 为此,它尝试将内部规则与AND "c"匹配
          • 谓词成功 - AND "c" 匹配内部规则
        • 将内部规则与AND "c"匹配
        • 匹配)
      • 谓词成功 - AND ( "b" AND "c" ) 匹配内部规则
    • 将内部规则与AND ( "b" AND "c" ) 匹配
      • AND 匹配,( 表示转到 and_thing
      • 要匹配and_thing,它必须:
        • 匹配("b"
        • 判断AND是否可以使用内部规则匹配
          • 为此,它尝试将内部规则与AND "c"匹配
          • 谓词成功 - AND "c" 匹配内部规则
        • 将内部规则与AND "c"匹配
        • 匹配)

    正如流程中强调的部分所示,ANTLR 需要匹配文本AND "c" 四次才能匹配输入,同时还有一层嵌套。如果有另一个层次,整个过程会重复两次,所以 ANTLR 会解析最后一部分 8 次。


    一个相关的评论 - 如果您使用语法谓词而不是回溯选项,您可以微调谓词包含的内容 - 在某些情况下,它不需要包含被谓词的整个规则。在上面的示例中,您可以告诉 ANLTR 在遇到OP_AND 时使用OP_AND and_level2_thing 规则,而无需检查and_level2_thing 是否匹配。请注意,您只能这样做,因为您知道 and_level2_thing 将匹配,或者另一个替代方案也将失败。如果你做错了,你最终会导致解析器迷路并拒绝一个如果它选择了正确的替代方案就会有效的输入。

    【讨论】:

    • 非常感谢@Jiri Tousek。 “然后 ANTLR 会告诉你语法在哪里不明确。” - 你的意思是在编译时,对吧?以下是有关删除回溯 theantlrguy.atlassian.net/wiki/display/ANTLR3/… 的一些信息。 “告诉 ANTLR 尝试所有替代方案并首先使用匹配的”
    • 它会在生成语法时报告这些(而不是在程序本身的编译时 - 而是在生成 Java 解析器类时)。
    • 我编辑了帖子以回答“如果第一个替代项匹配,为什么它仍然回溯” - 它不会回溯,但时间仍然是指数级的。
    【解决方案2】:

    添加 memoize “修复”问题。但我确信有更好的解决方案或围绕它进行一些更有趣的讨论。

    options
    {
        backtrack=true;
        memoize=true;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多