如何使用 Swift 5.7 的 RegexBuilder 捕获 10 多个事物?

2024-02-13

假设我有一个存储有关人员信息的文件,其中一行如下所示:

Sweeper 30 1992-09-22 China/Beijing - 0 2020-07-07 Mary/Linda - Pizza/Lemon

从左到右依次是姓名、年龄、出生日期、出生国家、出生城市、孩子数量、结婚日期(可选)、妻子姓名(可选)、前妻姓名(可选)、最喜欢的食物、最不喜欢的食物。

我想要得到all使用 Swift 5.7 RegexBuilder 模块的行中的信息,我尝试过:

let regex = Regex {
    /([a-zA-Z ]+)/ // Name
    " "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Age
    " "
    Capture(.iso8601Date(timeZone: .gmt)) // Date of Birth
    " "
    /([a-zA-Z ]+)/ // Country of Birth
    "/"
    /([a-zA-Z ]+)/ // City of Birth
    " - "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Children Count
    Optionally {
        " "
        Capture(.iso8601Date(timeZone: .gmt)) // Date of Marriage
        Optionally {
            " "
            /([a-zA-Z ]+)/ // Wife
            Optionally {
                "/"
                /([a-zA-Z ]+)/ // Ex-wife
            }
        }
    }
    " - "
    /([a-zA-Z ]+)/ // Favourite food
    "/"
    /([a-zA-Z ]+)/ // Least Favourite Food
}

然而,Swift 表示它无法在合理的时间内对此进行类型检查。

我知道发生这种情况的原因是因为RegexComponentBuilder https://developer.apple.com/documentation/regexbuilder/regexcomponentbuilder(正则表达式组件的结果生成器)最多只有 10 个“C”或类似的重载(不太确定细节):

static func buildPartialBlock<W0, W1, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, R0, R1>(
    accumulated: R0,
    next: R1) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10)> where R0 : RegexComponent, R1 : RegexComponent, R0.RegexOutput == (W0, C1, C2, C3), R1.RegexOutput == (W1, C4, C5, C6, C7, C8, C9, C10
)

如果我做了所有的Optionally parts required,错误消息变得更加明显。

“buildPartialBlock(accumulated:next:)”的使用不明确

SwiftUI 也有类似的问题,视图生成器中的视图数量不能超过 10,在这种情况下,您只需使用Group使某些观点成为single看法。你能在 RegexBuilder 中做类似的事情吗?将一些捕获设为单个捕获?好像有什么关系AnyRegexOutput,但我不知道如何使用它。

如何解决这个编译器错误?


为了避免 XY 问题:

我有一个数据文件,其中数据的格式非常随意,即根本不像 CSV 或 JSON 那样机器可读。线条以各种格式书写。随机分隔符用在随机的地方。

然后文件中的另一行将具有相同的信息,但格式不同。

我想要做的是将这个格式奇怪的文件转换为易于使用的格式,例如 CSV。我决定使用 Swift 5.7 RegexBuilder API 来完成此操作。我会在文件中找到一行,编写一个与该行匹配的正则表达式,将文件中与该正则表达式匹配的所有行转换为 CSV,然后冲洗并重复。

因此,我想避免使用多个正则表达式来解析单行,因为这意味着我将编写一个lot更多正则表达式。

我不确定像 ANTLR4 这样的解析器是否可以解决我的问题。考虑到文件格式的随机性,我需要更改解析器lot,导致文件一次又一次地生成。我认为这不会像使用 RegexBuilder 那样方便。


作为黑客,您可以创建一个广义的 CustomConsumingRegexComponent实施纳入

  • any RegexComponent由建造者建造,建造者总是有(Substring, A, B, C ...)元组作为输出
  • 将该元组转换为类型的转换T我们渴望的

我们基本上可以创建一个正则表达式组件,它接受一些正则表达式并输出任何类型T我们想要的,本质上是“分组”捕获。

也有可能不进行转换,最终会得到嵌套的元组,但我不喜欢这样。

struct Group<RegexOutput, Component: RegexComponent>: CustomConsumingRegexComponent {

    let component: () -> Component
    
    let transform: (Component.RegexOutput) -> RegexOutput
    
    init(@RegexComponentBuilder _ regexBuilder: @escaping () -> Component, transform: @escaping (Component.RegexOutput) -> RegexOutput) {
        component = regexBuilder
        self.transform = transform
    }
    
    func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: RegexOutput)? {
        let innerRegex = Regex(component)
        guard let match = input[index...].prefixMatch(of: innerRegex) else { return nil }
        let upperBound = match.range.upperBound
        let output = match.output
        let transformedOutput = transform(output)
        return (upperBound, transformedOutput)
    }
}

这只是一个 hack 的原因是因为里面的正则表达式Group实际上并不了解外界的事情Group,所以里面的量词Group不会回溯去尝试匹配之外的东西Group.

例如,要修复问题中的代码,我可以将所有与婚姻相关的信息放入Group,但我必须在其中添加一个前瞻Group:

struct Marriage {
    let marriageDate: Date
    let wife: Substring?
    let exWife: Substring?
}

let r = Regex {
    /([a-zA-Z ]+)/ // Name
    " "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Age
    " "
    Capture(.iso8601Date(timeZone: .gmt)) // Date of Birth
    " "
    /([a-zA-Z ]+)/ // Country of Birth
    "/"
    /([a-zA-Z ]+)/ // City of Birth
    " - "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Children Count

    Optionally {
        " "
        Capture(Group {
            Capture(.iso8601Date(timeZone: .gmt)) // Date of Marriage
            Optionally {
                " "
                /([a-zA-Z ]+)/ // Wife
                Optionally {
                    "/"
                    /([a-zA-Z ]+)/ // Ex-wife
                }
            }
            Lookahead(" - ")
        } transform: { (_, date, wife, exWife) in
            Marriage(marriageDate: date, wife: wife, exWife: exWife as? Substring) // unwrap the double optional
        })
    }
    " - "
    /([a-zA-Z ]+)/ // Favourite food
    "/"
    /([a-zA-Z ]+)/ // Least Favourite Food
}

如果没有前瞻,就会发生以下情况:

最里面的[a-zA-Z ]+会匹配Linda,以及它后面的空格,导致" - "不匹配。通常,这会导致回溯,但由于内部的东西Group不知道外界的事情Group,这里不发生回溯,整个匹配失败。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Swift 5.7 的 RegexBuilder 捕获 10 多个事物? 的相关文章

随机推荐