受 swift-parser-printer 启发的通用解析器-打印器
PartialIso
是一个值类型,它表示一个部分同构,即从 A
到 B
(apply
) 和从 B
到 A
(unapply
) 的一对可能失败的转换。有一些内置的 PartialIso
用于常见类型,例如从 String
转换为 Int
extension PartialIso where A == String, B == Int {
public static var int: PartialIso {
return PartialIso(
apply: { Int.init($0) },
unapply: String.init(describing:)
)
}
}
Parser
是一个值类型,负责解析顺序数据(不一定是字符串数据),以及“打印”它,这是一种将解析的数据转换为解析器期望的输入类型并可选择地为输入数据创建模板的操作。
例如,我们可以定义一个解析器,它负责解析 URL 路径中的字符串字面量
public func path(_ str: String) -> Parser<URLComponents, Prelude.Unit> {
return Parser<URLComponents, Prelude.Unit>.init(
parse: { components in
let pathComponents = components.path.components(separatedBy: "/")
return components.pathComponents.head().flatMap { (p, ps) in
return p == str
? (components.with { $0.path = ps.joined(separator: "/") }, unit)
: nil
}
},
print: { _ in URLComponents().with { $0.path = str } },
template: { _ in URLComponents().with { $0.path = str } }
)
}
Parser
的输入类型应该实现 Prelude.Monoid
协议
import Prelude
extension URLComponents: Monoid {
public static var empty: URLComponents { return URLComponents() }
public static func <>(lhs: URLComponents, rhs: URLComponents) -> URLComponents {
// create URLComponents by composing lhs and rhs components
}
}
FormatType
是一个协议,你的类型可以实现它,以便它可以利用 Parser
解析输入。 当符合此协议时,你将免费获得 match
、print
和 template
函数,但如果需要,你可以覆盖它们。它还附带一些运算符,例如 <¢>
(map
) 和 <|>
,你将使用它们将简单的格式组合成更复杂的格式。
例如,使用 <|>
和 reduce
函数,你可以将多个格式组合成一个可以解析这些格式中任何一个的格式
public struct URLFormat<A>: FormatType { ... }
let formats: URLFormat =
scheme("https") </> host("me.com") </> [
// matches URLs with "/echo" path
path("echo"),
// matches URLs with "/hello" path
path("hello")
].reduce(.empty, <|>)
formats.match(URLComponents(string: "https://me.com/echo")!) // Prelude.unit
formats.match(URLComponents(string: "https://me.com/hello")!) // Prelude.unit
formats.match(URLComponents(string: "https://me.com/echo/hello")!) // nil
注意:你可以从
FormatType
元素的数组字面量创建相同的FormatType
。
当所有格式都具有相同的类型时,这效果很好,例如 URLFormat<Prelude.Unit>
或 URLFormat<String>
,但无法将 URLFormat<Prelude.Unit>
与 URLFormat<String>
组合。 为此,你可以为这样的格式化程序定义一个和类型(在 Swift 中是一个 enum
),并使用 <¢>
运算符。
enum Routes {
case echo
case hello(String)
}
let formats: URLFormat =
scheme("https") </> host("me.com") </> [
// matches URLs with "/echo" path
iso(Routes.echo) <¢> path("echo"),
// matches URLs with "/hello/:string" path
iso(Routes.hello) <¢> path("hello") </> path(.string)
].reduce(.empty, <|>)
formats.match(URLComponents(string: "https://me.com/echo")!) // Routes.echo
formats.match(URLComponents(string: "https://me.com/hello/world")!) // Routes.hello("world")
这里的 iso
是一个辅助函数,可以在实现 Matchable
协议的类型上使用。 或者,你可以像这样为你的和类型定义部分同构
extension Routes {
enum iso {
static let echo = PartialIso(
apply: { (_: Prelude.Unit) -> Routes? in
return .echo
},
unapply: { (r: Routes) -> Prelude.Unit? in
return Prelude.unit
})
static let hello = PartialIso(
apply: { (s: String) -> Routes? in
return .hello(s)
},
unapply: { (r: Routes) -> String? in
guard case let .hello(str) = r else { return nil }
return str
})
}
}
let formats: URLFormat =
scheme("https") </> host("me.com") </> [
// matches URLs with "/echo" path
Routes.iso.echo <¢> path("echo"),
// matches URLs with "/hello/:string" path
Routes.iso.hello <¢> path("hello") </> path(.string)
].reduce(.empty, <|>)
Matchable
协议是一个辅助协议,可简化为你的和类型定义部分同构。 无需为每个案例实现部分同构,你可以实现一个单一的方法,在该方法中你可以使用简单的模式匹配。 然后,你可以将 iso
函数与枚举案例构造器一起使用,为这些枚举案例创建部分同构。
extension Routes: Matchable {
func match<A>(_ constructor: (A) -> Routes) -> A? {
switch self {
case let .hello(values):
guard let a = values as? A, self == constructor(a) else { return nil }
return a
case .echo:
guard let a = unit as? A, self == constructor(a) else { return nil }
return a
default: return nil
}
}
}
let formats: URLFormat =
scheme("https") </> host("me.com") </> [
// matches URLs with "echo/" path
iso(Routes.echo) <¢> path("echo"),
// matches URLs with "hello/:string" path
iso(Routes.hello) <¢> path("hello") </> path(.string)
].reduce(.empty, <|>)
注意:你可以使用 Sourcery 或 SwiftSyntax 为
Matchable
实现或为每个案例的单独iso
生成代码。
Parser
不仅可以将任意输入解析为任意输出类型,还可以用于基于输出生成输入。 例如,使用 URLFormat
,我们可以打印任意路由的 URL
try formats.print(.echo)!.render() // "https://me.com/echo"
try formats.print(.hello("world"))!.render() // "https://me.com/hello/world"
当你需要提供获得特定输出所需的输入时,这会很有帮助,即在 URL 的情况下,你可以使用它来渲染指向特定内容的 URL。
你也可以使用 template
函数来获取类似模板的输入
try formats.template(.echo)!.render() // "https://me.com/echo"
try formats.template(.hello("world"))!.render() // "https://me.com/hello/:string"
这对于诸如文档、日志或错误消息之类的东西很有用。
import PackageDescription
let package = Package(
dependencies: [
.package(url: "https://github.com/ilyapuchka/common-parsers.git", .branch("master")),
]
)