Carthage compatible

Swift 5、4.2 和 4.1 | Swift 2.2+

FootlessParser

FootlessParser 是 Swift 中一个简单且相当简陋的解析器组合子实现。它支持无限前瞻、无歧义解析以及错误报告。

这里有一系列关于开发的博客文章,以及来自源代码的文档

简介

简而言之,FootlessParser 允许您像这样定义解析器:

let parser = function1 <^> parser1 <*> parser2 <|> parser3

function1parser3 返回相同的类型。

parser 会将输入传递给 parser1,然后是 parser2,并将它们的结果传递给 function1 并返回其结果。如果失败,它会将原始输入传递给 parser3 并返回其结果。

定义

解析器 (Parser)

一个函数,它接受一些输入(token 序列),并返回输出以及输入中剩余的未解析部分,如果解析失败,则返回错误描述。

Token (令牌)

来自输入的单个项目。例如,字符串中的字符、数组中的元素或命令行参数列表中的字符串。

解析器输入 (Parser Input)

通常是文本,但也可以是数组或任何集合,只要它符合 CollectionType 即可。

解析器 (Parsers)

总体的想法是将非常简单的解析器组合成更复杂的解析器。因此,char("a") 创建一个解析器,该解析器检查来自输入的下一个 token 是否为 "a"。如果是,它返回 "a",否则返回错误。然后,您可以使用运算符和函数(例如 zeroOrMoreoptional)来创建越来越复杂的解析器。有关更多信息,请查看函数的完整列表

运算符 (Operators)

<^> (map)

function <^> parser1 创建一个新的解析器,该解析器运行 parser1。如果成功,它会将输出传递给 function 并返回结果。

<*> (apply)

function <^> parser1 <*> parser2 创建一个新的解析器,该解析器首先运行 parser1。如果成功,它运行 parser2。如果也成功,它会将两个输出都传递给 function 并返回结果。

<*> 运算符要求其左侧的解析器返回一个函数,并且通常与 <^> 一起使用。function 必须接受 2 个正确类型的参数,并且必须是柯里化的,如下所示:

func function (a: A) -> (B) -> C

这是因为 <*> 返回 2 个解析器的输出,并且它不知道如何处理它们。如果您想以元组、数组或例如加在一起的形式返回它们,您可以在 <^> 之前的函数中这样做。

如果有 3 个解析器和 2 个 <*>,则该函数必须接受 3 个参数,依此类推。

<*

与上面的 <*> 相同,但它会丢弃右侧解析器的结果。由于它只返回一个输出,因此不需要与 <^> 一起使用。 但当然,如果您想将输出转换为其他内容,也可以这样做。

*>

与 <* 相同,但会丢弃左侧解析器的结果。

<|> (choice)

parser1 <|> parser2 <|> parser3

此运算符按顺序尝试所有解析器,并返回第一个成功的解析器的结果。

>>- (flatmap)

parser1 >>- ( o -> parser2 )

这与 Swift 标准库中的 flatmap 函数相同。它创建一个新的解析器,该解析器首先尝试 parser1。如果失败,它将返回错误,如果成功,它会将输出传递给函数,该函数使用它来创建 parser2。然后它运行 parser2 并返回其输出或错误。

示例

实际使用

CSV 解析器

let delimiter = "," as Character
let quote = "\"" as Character
let newline = "\n" as Character

let cell = char(quote) *> zeroOrMore(not(quote)) <* char(quote)
	<|> zeroOrMore(noneOf([delimiter, newline]))

let row = extend <^> cell <*> zeroOrMore(char(delimiter) *> cell) <* char(newline)
let csvparser = zeroOrMore(row)

此处,单元格(或字段)可以:

然后,每一行由一个或多个单元格组成,单元格之间用逗号分隔,并以换行符结尾。 extend 函数将单元格连接成一个数组。最后,csvparser 将零行或多行收集到一个数组中。

要执行实际的解析:

do {
    let output = try parse(csvparser, csvtext)
    // output is an array of all the rows,
    // where each row is an array of all its cells.
} catch {

}

递归表达式

func add(a: Int) -> (Int) -> Int { return { b in a + b } }
func multiply(a: Int) -> (Int) -> Int { return { b in a + b } }

let nr = { Int($0)! } <^> oneOrMore(oneOf("0123456789"))

var expression: Parser<Character, Int>!

let factor = nr <|> lazy (char("(") *> expression <* char(")"))

var term: Parser<Character, Int>!
term = lazy (multiply <^> factor <* char("*") <*> term <|> factor)

expression = lazy (add <^> term <* char("+") <*> expression <|> term)

do {
	let result = try parse(expression, "(1+(2+4))+3")
} catch {

}

expression 解析诸如 "12""1+2+3""(1+2)""12*3+1""12*(3+1)" 之类的输入,并将结果作为 Int 返回。

所有直接或间接引用自身的解析器都必须预先声明为变量隐式解包的可选项 (var expression: Parser<Character, Int>!)。为了避免无限递归,定义必须使用 lazy 函数。

安装

使用 Carthage

github "kareman/FootlessParser"

然后运行 carthage update

然后按照 Carthage 的 README 中的安装说明进行操作。

使用 CocoaPods

FootlessParser 添加到您的 Podfile 文件中。

pod 'FootlessParser', '~> 0.5.2'

然后运行 pod install 来安装它。