Build Status Platforms Documentation Discord

SwiftTreeSitter

Swift API,用于 tree-sitter 增量解析系统。

结构

这个项目实际上分为两个部分:SwiftTreeSitterSwiftTreeSitterLayer

SwiftTreeSitter 目标与 C 运行时 API 非常接近。它只添加了少量额外的类型来帮助支持查询。它相当底层,在实际项目中使用它需要做大量工作。

SwiftTreeSitterLayer 是构建在 SwiftTreeSitter 之上的抽象层。它支持包含嵌套语言的文档,以及跨这些嵌套的透明查询。它还支持异步语言解析。虽然仍然是底层,但 SwiftTreeSitterLayer 更易于使用,同时也支持更多功能。

还有更多!如果您正在寻找用于语法高亮和其他语法操作的更高级系统,您可能需要看看 Neon。它更容易与文本系统集成,并且具有许多额外的性能相关功能。

集成

dependencies: [
    .package(url: "https://github.com/ChimeHQ/SwiftTreeSitter")
],
targets: [
    .target(
        name: "MySwiftTreeSitterTarget",
        dependencies: ["SwiftTreeSitter"]
    ),
    .target(
        name: "MySwiftTreeSitterLayerTarget",
        dependencies: [
            .product(name: "SwiftTreeSitterLayer", package: "SwiftTreeSitter"),
        ]
    ),
]

范围转换

tree-sitter 运行时操作原始字符串数据。这意味着它处理字节,并且对字符串编码敏感。Swift 的 String 类型是原始数据之上的抽象,不能直接使用。为了克服这个问题,您还需要了解您正在使用的索引类型,以及字符串数据如何来回转换。

为了帮助您,SwiftTreeSitter 支持基本的 tree-sitter 编码功能。您可以通过 Parser.parse(tree:encoding:readBlock:) 控制这一点。但是,默认情况下,这将假定为 UTF-16 编码的数据。这样做是为了提供与 Foundation 字符串和 NSRange 的直接兼容性,它们都使用 UTF-16。

此外,为了帮助处理所有的来回转换,SwiftTreeSitter 包含一些基于 NSRange 的访问器,以及 NSRange 的扩展。除非您自己注意处理编码,否则在使用原生 tree-sitter 类型时必须使用这些。

为了保持清晰,使用了 consistent 的命名和类型。Node.byteRange 返回一个 Range<UInt32>,这是一个依赖于编码的值。Node.range 是一个 NSRange,它被定义为使用 UTF-16。

let node = tree.rootNode!

// this is encoding-dependent and cannot be used with your storage
node.byteRange

// this is a UTF-16-assumed translation of the byte ranges
node.range

// converting UTF-16-based changed ranges on re-parse
let ranges: [NSRange] = newtree.changedRanges(from: oldTree)
    .map{ $0.bytes.range }

查询冲突

SwiftTreeSitter 尽最大努力解决较差/不正确的查询构造,这些构造出奇地常见。

当使用注入时,子查询范围会自动使用父匹配项进行扩展。这处理了父项的查询以冲突方式与子项重叠的情况。如果没有扩展,则可能会构造出落在子范围内的查询,但会在父匹配项上产生结果。

所有匹配项都按以下顺序排序

即使有了这些,也可能产生导致“不正确”行为的查询,这些行为在查询定义中要么是模棱两可的,要么是未定义的。

高亮

tree-sitter 的一个非常常见的用途是进行语法高亮。可以直接使用此库,特别是如果您的源代码文本不更改。这是一个小例子,展示了如何使用 SPM 捆绑的语言进行设置。

首先,了解一下它如何与 SwiftTreeSitterLayer 一起工作。它很复杂,但为您做了很多事情。

// LanguageConfiguration takes care of finding and loading queries in SPM-created bundles.
let markdownConfig = try LanguageConfiguration(tree_sitter_markdown(), name: "Markdown")
let markdownInlineConfig = try LanguageConfiguration(
    tree_sitter_markdown_inline(),
    name: "MarkdownInline",
    bundleName: "TreeSitterMarkdown_TreeSitterMarkdownInline"
)
let swiftConfig = try LanguageConfiguration(tree_sitter_swift(), name: "Swift")

// Unfortunately, injections do not use standardized language names, and can even be content-dependent. Your system must do this mapping.
let config = LanguageLayer.Configuration(
    languageProvider: {
        name in
        switch name {
        case "markdown":
            return markdownConfig
        case "markdown_inline":
            return markdownInlineConfig
        case "swift":
            return swiftConfig
        default:
            return nil
        }
    }
)

let rootLayer = try LanguageLayer(languageConfig: markdownConfig, configuration: config)

let source = """
# this is markdown

```swift
func main(a: Int) {
}
```

## also markdown

```swift
let value = "abc"
```
"""

rootLayer.replaceContent(with: source)

let fullRange = NSRange(source.startIndex..<source.endIndex, in: source)

let textProvider = source.predicateTextProvider
let highlights = try rootLayer.highlights(in: fullRange, provider: textProvider)

for namedRange in highlights {
    print("\(namedRange.name): \(namedRange.range)")
}

您也可以直接使用 SwiftTreeSitter

let swiftConfig = try LanguageConfiguration(tree_sitter_swift(), name: "Swift")

let parser = Parser()
try parser.setLanguage(swiftConfig.language)

let source = """
func main() {}
"""
let tree = parser.parse(source)!

let query = swiftConfig.queries[.highlights]!

let cursor = query.execute(in: tree)
let highlights = cursor
    .resolve(with: .init(string: source))
    .highlights()

for namedRange in highlights {
    print("range: ", namedRange)
}

语言解析器

Tree-sitter 语言解析器是单独的项目,您可能至少需要一个。更多详细信息请参阅文档。它们的安装和集成方式各不相同。

这是支持 SPM 的解析器列表。既然您在这里,您可能会觉得这很方便。并且 LanguageConfiguration 类型支持直接加载捆绑的查询。

解析器 Make SPM 官方仓库
Bash
C
C++
C#
Clojure
CSS
Dockerfile
Diff
Elixir
Elm
Go
GoMod
GoWork
Haskell
HCL
HTML
Java
Javascript
JSON
JSDoc
Julia
Kotlin
Latex
Lua
Markdown
OCaml
Perl
PHP
Pkl
Python
Ruby
Rust
Scala
SQL
SSH
Swift
TOML
Tree-sitter 查询语言
Typescript
Verilog
YAML
Zig

贡献与协作

我很乐意收到您的来信!问题或拉取请求都很好。Discord 服务器也提供实时帮助,但我强烈倾向于以文档的形式回答。

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

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

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