Flexer 是一个用于在 Swift 中构建词法分析器的小型库。它兼容所有 Apple 平台。
String
兼容Sequence
和 IteratorProtocol
协议事实证明,Swift 的 Sequence
和 Iterator
概念在处理令牌时效果非常好。 它们提供了一个熟悉的 API,同时也提供了惊人的功能。 Flexer 在这些概念的基础上构建了一些专门为词法分析设计的新协议,但通常适用于所有 Sequence
类型。
dependencies: [
.package(url: "https://github.com/ChimeHQ/Flexer")
]
词法分析的核心是能够在不前进的情况下查看未来的令牌。 Flexer 使用一个名为 LookAheadIteratorProtocol
的协议来实现前瞻。 整个实现灵感来自 Sequence
的 lazy
属性,并且工作方式非常相似。
let lookAheadSequence = anySequence.lookAhead
let next = lookAheadSequence.peek()
构建词法分析器的主要工作是定义一个令牌的 Sequence 类型。 然后,您可能需要的所有词法分析工具都可以通过 typealias
公开。
typealias MyLexer = LookAheadSequence<MyTokenSequence>
let tokenSequence = MyLexer(string: myString)
let nextToken = lexer.next()
let futureToken = lexer.peek()
let tabToken = lexer.nextUntil({ $0.kind == .tab })
您可以通过创建一个符合 Sequence
的结构体来构建自定义的令牌序列。 为了使这个过程更容易,Flexer 包含一个可以用作创建更复杂令牌流基础的类型,称为 BasicTextCharacterSequence
。 它是 BasicTextCharacter
元素的序列。 它将字符串分解为常用的令牌,并按种类和源字符串中的范围进行分类。 这种方法使用 Token
类型,该类型存储种类和源字符串中的范围。
通常,使用 Swift switch 模式匹配的便利性来构建更复杂的词法分析功能要容易得多,而不是担心底层字符和范围本身。 您可以通过将 BasicTextCharacterSequence
包装到您自己的自定义序列中来做到这一点。
这是一个功能完整的示例,它产生四种不同的令牌类型。 它展示了一些扫描和前瞻工具,这些工具对于构建和使用词法分析器都很有用。
enum ExampleTokenKind {
case word
case number
case symbol
case whitespace
}
typealias ExampleToken = Flexer.Token<ExampleTokenKind>
struct ExampleTokenSequence: Sequence, IteratorProtocol, StringInitializable {
public typealias Element = ExampleToken
private var lexer: BasicTextCharacterLexer
public init(string: String) {
self.lexer = BasicTextCharacterLexer(string: string)
}
public mutating func next() -> Element? {
guard let token = lexer.peek() else {
return nil
}
switch token.kind {
case .lowercaseLetter, .uppercaseLetter, .underscore:
guard let endingToken = lexer.nextUntil(notIn: [.lowercaseLetter, .uppercaseLetter, .underscore, .digit]) else {
return nil
}
return ExampleToken(kind: .word, range: token.startIndex..<endingToken.endIndex)
case .digit:
guard let endingToken = lexer.nextUntil({ $0.kind != .digit }) else {
return nil
}
return ExampleToken(kind: .number, range: token.startIndex..<endingToken.endIndex)
case .newline, .tab, .space:
guard let endingToken = lexer.nextUntil(notIn: [.newline, .tab, .space]) else {
return nil
}
return ExampleToken(kind: .whitespace, range: token.startIndex..<endingToken.endIndex)
default:
break
}
guard let endingToken = lexer.nextUntil(in: [.newline, .tab, .space, .lowercaseLetter, .uppercaseLetter, .underscore, .digit]) else {
return nil
}
return ExampleToken(kind: .symbol, range: token.startIndex..<endingToken.endIndex)
}
}
typealias ExampleTokenLexer = LookAheadSequence<ExampleTokenSequence>
我很乐意听到您的声音! Issues 或 pull requests 效果很好。 Matrix space 和 Discord 都可以提供实时帮助,但我强烈倾向于以文档的形式回答。 您也可以在 mastodon 上找到我。
我更喜欢协作,如果您有类似的项目,我很乐意找到合作的方式。
我更喜欢使用制表符进行缩进,以提高可访问性。 但是,我宁愿您使用您想要的系统并创建一个 PR,也不愿因为空格而犹豫不决。
通过参与这个项目,您同意遵守 贡献者行为准则。