在 raku 的语法中使用 'after' 作为 lookbehind

Using 'after' as lookbehind in a grammar in raku

我正在尝试使用 raku 语法进行匹配,但因 'after' 而失败。我已将我的问题归结为以下片段:

grammar MyGrammar {

    token TOP {
        <character>
    }

    token character {
        <?after \n\n>LUKE
    }
}

say MyGrammar.subparse("\n\nLUKE");

这 returns #<failed match> 作为 MyGrammar.subparse 和 Nil 作为 MyGrammar.parse.

但是如果我 运行 在 REPL 中匹配:

"\n\nLUKE" ~~ /<?after \n\n>LUKE/

我得到匹配「LUKE」

所以有些事情我不明白,我不确定是什么。有什么指点吗?

<?after ...>不推进匹配游标

这里重要的是 <?after \n\n>"zero width" assertion

如果匹配光标位于被匹配字符串中 "\n\n" 的紧邻右侧,则匹配,但不会使匹配光标前进。

为什么 ~~ / ... / 版本匹配

regex/grammar 引擎自动为您推进匹配光标。

普通正则表达式风格的匹配与传统正则表达式一样工作。特别是,它应该匹配正在匹配的字符串中的 anywhere,除非您显式添加锚点,例如 ^(字符串开头)and/or $(字符串结尾)。

更明确地说,匹配引擎将首先尝试匹配正在匹配的字符串的第一个字符位置。然后,如果失败,它会自动在字符串中向前移动一个字符,然后再次尝试从正则表达式模式的开头进行匹配。

所以所有这些也将匹配并给出相同的结果:

"\n\nLUKE" ~~ /LUKE/;                     # 「LUKE」
"\n\nLUKE" ~~ /LUKE $/;                   # 「LUKE」
"LUKE"     ~~ /^ LUKE $/;                 # 「LUKE」
"\n\nLUKE" ~~ / <?after \n\n>LUKE $/;     # 「LUKE」

为什么语法版本不匹配

语法应从输入字符串的开头开始匹配。否则失败。

更明确地说,.parse 在解析的开始和结束处具有隐式 ^$ 锚点,而 .subparse 具有隐式 ^在开头。

如果匹配游标未能通过第一个字符,则解析失败。您的语法不会使匹配光标超过第一个字符,因此失败。

( <?after \n\n> 如果匹配则不仅不会使游标前进,而且它甚至从一开始就不会匹配——因为在字符串的开头,匹配游标仅在 [=51 之后=]nothing。如果你改为写 <?after ''>,那么它总是会成功,但仍然不会使光标前进,所以如果这是你所做的唯一更改,语法仍然会失败。)

当我们使用语法解析字符串时,匹配锚定在字符串的开头。使用 parse 解析输入需要我们使用所有字符串。还有一个 subparse,它允许我们不消耗所有输入,但这仍然锚定在字符串的开头。

相比之下,像 /<?after \n\n>LUKE/ 这样的正则表达式将 扫描 整个字符串,尝试匹配字符串中每个位置的模式,直到它找到一个位置它匹配(或到达字符串的末尾并放弃)。这就是它起作用的原因。但是请注意,如果您的目标是不捕获 \n\n,那么您可以将正则表达式编写为 /\n\n <( LUKE/,其中 <( 表示从哪里开始捕获。至少在目前的Rakudo编译器实现上,这种方式效率更高

在没有更多上下文的情况下建议如何编写语法并不容易(我猜这是从一个更大的问题中提取的)。例如,您可以在语法的开头使用空格:

grammar MyGrammar {

    token TOP {
        \s+ <character>
    }

    token character {
        <?after \n\n>LUKE
    }
}

say MyGrammar.subparse("\n\nLUKE");

或者使用字符中的 \n\n 但将其从与 <( 的匹配中排除,如前所述。

目前的答案都很好,但让我更详细地解释一下误解的根源。 要点是,在这里您将作为语法一部分的标记与独立的正则表达式进行比较。它们使用相同的语言、正则表达式,但它们并不相同。可以用正则匹配,代入提取信息; token 的 objective 纯粹是提取信息;从具有常规结构的字符串中,我想要一个部分并且只是那个部分。 我假设您对 LUKE 部分感兴趣,并且您正在使用 <after 来表达“不,这不是我感兴趣的”,或者“跳过这个,只给我货物”。 Jonathan 已经说过一种方法,可能是最好的方法:

grammar MyGrammar {

    token TOP {
        <character>
    }

    token character {
         \n \n <( LUKE
    }
}

say MyGrammar.subparse("\n\nLUKE");

不仅会数学,而且只会抓拍LUKE:

「

LUKE」
 character => 「LUKE

跳过那个。但是,语法不匹配,它们提取。所以你可能希望分隔符也出现在语法中,而不值得一遍又一遍地重复它们。此外,一般语法旨在自上而下使用。所以这样做:

grammar MyGrammar {

    token TOP {
        <separator><character>
    }

    token separator { \n \n }
    token character { <[A..Z]>+  }
}

say MyGrammar.parse("\n\nLUKE");

character 标记现在更通用(尽管它可能会使用一些空格,我不知道。同样,也许您对分隔符不感兴趣。只需使用点来忽略它. 只是因为你不感兴趣并不意味着你不必解析它,语法给了你一个方法:

grammar MyGrammar {

    token TOP {
        <.separator><character>
    }

    token separator { \n \n }
    token character { <[A..Z]>+  }
}

say MyGrammar.parse("\n\nLUKE");

这个给出了相同的结果:

「

LUKE」
 character => 「LUKE」

归根结底,语法和正则表达式有不同的用例,因此相同 objective 的解决方案也不同。以正确的方式思考它们可以为您提供有关如何构建它们的提示。