语法中的部分匹配

Partial Match in a Grammar

我有一个简单的语法,我正在用它来解析一些文本。文本是用户输入的,但我的程序保证它的星标与语法匹配。 (即,如果我的语法只匹配 a,则文本可能是 abcaa_。)但是,当我使用 .parse 方法时我的语法,它在任何非精确匹配上都失败了。如何执行部分匹配?

TL;DR

grammar foo { token TOP { a* } } 

# Partial match anchored at start of string:
say .subparse: 'abcaa' given foo; # 「a」 

# Partial match anchored to end of string:
say 'abcaa' ~~ / <.foo::TOP> $ /; #  「aa」

# Longest partial match, no anchoring:
say ('abcaaabcaabc' ~~ m:g/ <.foo::TOP> /).max(*.chars); #  「aaa」

词汇

传统上有两种关于文本“匹配”的一般概念:

  • “正在解析”

  • “正则表达式”

乐:

  • 提供统一的文本模式语言和引擎来完成这两项工作。

  • 可以很容易地坚持一种或另一种观点,或者混合它们,或者在它们之间重构,以适应个人开发 and/or 个人用例。

  • “解析”或多或少意味着从输入字符串的开头开始的单个匹配项,而“正则表达式”则更加灵活。

您在问题中所写的内容以及您对 Tyil 回答的第一条评论反映了该主题固有的歧义。我将提供两个答案而不是一个来尝试帮助您 and/or 其他读者更清楚 Raku 对词汇的使用,以及您的选项功能明智。

有限“部分匹配”通过.parse

您的开头是:

Partial match in a grammar ... I have a simple grammar ... my program guarantees that it starts with a match to the grammar

考虑到这一点,这是你的问题:

How can I perform a partial match?

短语“保证它开始”和“部分匹配”是不明确的。

一种情况是您需要我称之为“前缀”的匹配,匹配从字符串开头锚定的一个或多个字符,而不仅仅是输入字符串中任意位置开始和结束的任何子字符串.

这非常适合“解析”,或者至少 Raku 在其语法方法中使用该词。

All 名称中带有 parse 的内置 Grammar 方法在它们使用的任何语法规则中插入一个锚点到字符串的开头开始解析过程。 你不能删除那个锚点。这反映了词汇的选择; “解析”意味着匹配 从头开始,无论发生什么情况。

这个“前缀”场景的解析方法是.subparse:

grammar foo { token TOP { a* } } 

# Partial match anchored at start of string:
say .subparse: 'abcaa' given foo; # 「a」 

另请参阅:


但也许“保证它开始”和“部分匹配”不是意味着您想要在开始时锚定。您对 Tyil 的回答的评论突出了这种歧义:

Will .subparse only match at the start, or match anywhere in the string?

Tyil 提供了一种解决方法。您 可以 执行 Tyil 显示的操作,但只有当输入字符串中遇到的第一个 a 是位于您的子字符串开头的那个时,它才会匹配希望你的“解析”匹配。

如果第一个 a 是误报,并且有一个 第二个 或随后的 a 您希望“解析”匹配那么,至少在 Raku 世界中,将其称为“正则表达式”而不是“解析”并通过 the ~~ smartmatch operator.

使用“正则表达式”匹配是有帮助的

Un通过~~

限制“部分匹配”

如果您将其 ~~ 构造与正则表达式一起使用,无限 Raku 允许您进行部分匹配。

例如,您可以这样写:

# End of match at end of string:
                          ↓
say 'abcaa' ~~ token { a* $ } #  「aa」

~~ 使用正则表达式告诉 Raku:

  • 尝试从 LHS 上字符串的第一个字符位置开始匹配;

  • 如果失败,向前移动一个字符,然后重试,将输入字符串中的新位置视为新的起点;

  • 重复直到匹配一次,或者在整个字符串中找不到任何匹配项。

这里我没有指定匹配的开始位置(~~ 意味着它可以在字符串中的任何位置)并将模式的结尾锚定到输入字符串的结尾。所以它成功匹配了字符串末尾的aa

这种锚定自由度只是 ~~ 智能匹配提供比使用 parse 方法更大的匹配灵活性的众多方式之一。


如果您已有语法,您仍然可以使用它:

grammar foo { token TOP { a* } } 

# Anchor matching to end of string:
                             ↓
say 'abcaa' ~~ / <.foo::TOP> $ /; #  「aa」

您必须在其中命名要调用的语法和规则,并将它们放入 <...>。你需要插入一个 . 来避免相应命名的子捕获,假设你不想要那个。


这是另一个例子:

# Longest partial match, no anchoring:
say ('abcaaabcaabc' ~~ m:g/ <.foo::TOP> /).max(*.chars); #  「aaa」

Raku 中的“解析”始终从输入字符串的开头开始 并导致不匹配或 一个 匹配.

相比之下,“regex”可以匹配任意片段,并且可以匹配任意数量的片段。 (您甚至可以匹配重叠的片段。)

在上一个示例中,我使用了 :g,它是 :global 的缩写,这是传统正则表达式引擎中众所周知的功能。 :g 与在输​​入字符串中找到匹配项的次数相同(但不重叠)。

匹配操作然后 returns Nil(根本没有匹配)或 list 匹配对象(一个或多个)。我应用了 .max(*.chars) 来产生最长的匹配(如果有多个最长的子字符串,则为第一个)。

在 Raku 中,Grammar.parse 必须匹配整个字符串。如果您的语法只匹配字符串 abc 中的 a,这就是导致它失败的原因。要仅匹配输入字符串的一部分,您可以改用 Grammar.subparse

grammar Foo {
    token TOP { 'a' }
}

my $string = 'abc';

say Foo.parse($string);    # Nil
say Foo.subparse($string); # 「a」

输入字符串需要以潜在的 Match 开头。否则,您将匹配失败。

say Foo.subparse('cbacb'); # #<failed match>

您可以使用 Capture marker 解决此问题。

grammar Bar {
    token TOP {
        <-[a]>*   # Match 0 or more characters that are *not* a
        <( 'a'    # Start the match, and match a single 'a'
    }
}

say Bar.parse('a');        # 「a」
say Bar.subparse('a');     # 「a」
say Bar.parse('abc');      # Nil
say Bar.subparse('abc');   # 「a」
say Bar.parse('cbabc');    # Nil
say Bar.subparse('cbabc'); # 「a」

这是有效的,因为 <-[a]>*,一个包含任何字符 除了 字母 a 的字符 class 将消耗之前的所有字符一个潜在的a。但是,Capture 标记将导致这些从最终的 Match 对象中删除,只留下您想要匹配的 a