Build Status Platforms Matrix

Flexer

Flexer 是一个用于在 Swift 中构建词法分析器的小型库。它兼容所有 Apple 平台。

事实证明,Swift 的 SequenceIterator 概念在处理令牌时效果非常好。 它们提供了一个熟悉的 API,同时也提供了惊人的功能。 Flexer 在这些概念的基础上构建了一些专门为词法分析设计的新协议,但通常适用于所有 Sequence 类型。

集成

dependencies: [
    .package(url: "https://github.com/ChimeHQ/Flexer")
]

前瞻

词法分析的核心是能够在不前进的情况下查看未来的令牌。 Flexer 使用一个名为 LookAheadIteratorProtocol 的协议来实现前瞻。 整个实现灵感来自 Sequencelazy 属性,并且工作方式非常相似。

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 spaceDiscord 都可以提供实时帮助,但我强烈倾向于以文档的形式回答。 您也可以在 mastodon 上找到我。

我更喜欢协作,如果您有类似的项目,我很乐意找到合作的方式。

我更喜欢使用制表符进行缩进,以提高可访问性。 但是,我宁愿您使用您想要的系统并创建一个 PR,也不愿因为空格而犹豫不决。

通过参与这个项目,您同意遵守 贡献者行为准则