SwiftTreeSitter Documentation

repository·main·Indexed 19 days ago

https://github.com/tree-sitter/swift-tree-sitter

A Swift API for the tree-sitter incremental parsing system. It provides a low-level C-compatible API (SwiftTreeSitter) and a high-level abstraction (SwiftTreeSitterLayer) for complex tasks such as nested language handling and syntax highlighting. The library includes components for parsing, tree traversal, and pattern matching via queries, and supports integrating language parsers through Swift Package Manager, manual builds, or Make.

Tokens
2.8K
Snippets
4
Records
10
Agent score
63%

What's inside SwiftTreeSitter

  1. Overview of SwiftTreeSitter

    main

    SwiftTreeSitter is a Swift API designed to map the tree-sitter C API closely. It provides a Swift-idiomatic interface for the incremental parsing system.

    Key differences from the original C API:

    1. Swift/Foundation Types: It utilizes Swift and Foundation types where appropriate to improve developer experience.
    2. Query Resolution: It offers a specialized query resolution system centered around ResolvingQueryMatchSequence.

    For low-level implementation details not covered in the Swift documentation, developers should refer to the official tree-sitter C API documentation.

  2. Handle string encoding and range translation

    main

    Tree-sitter operates on raw bytes, which is encoding-sensitive. Because Swift String is an abstraction, you must handle the translation between byte offsets and Swift string indexes.

    By default, Parser.parse(tree:encoding:readBlock:) assumes UTF-16-encoded data to maintain compatibility with Foundation strings and NSRange.

    Important: To avoid manual encoding errors, use the provided NSRange-based accessors and extensions.

    • Node.byteRange returns a Range<UInt32> (encoding-dependent).
    • Node.range returns an NSRange (assumes UTF-16).

    When working with changed ranges during a re-parse, map the byte ranges to NSRange using the .bytes.range property.

    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 }
  3. Core Components of SwiftTreeSitter

    main

    SwiftTreeSitter is organized into several functional areas:

    Parsing

    Handles the transformation of source code into syntax trees. Key types include:

    • Parser: The main entry point for parsing.
    • Language: Represents the grammar of a specific language.
    • InputEdit: Describes changes made to the source text for incremental parsing.

    Trees

    Provides access to the resulting syntax structures. Key types include:

    • Tree: The root structure representing the entire parsed document.
    • Node: An individual element within the syntax tree.
    • TreeCursor: An efficient way to traverse the tree.

    Queries

    Used for pattern matching within the syntax tree. Key types include:

    • Query: The pattern to match.
    • QueryCursor: An iterator for finding matches.
    • ResolvingQueryMatchSequence: A system for resolving query matches.
    • QueryCapture, QueryMatch, QueryError, Predicate, QueryPredicateError, and QueryPredicateStep.

    Structures

    Basic geometric and positional types:

    • TSRange: Represents a range of text.
    • Point: Represents a specific position in the text.
  4. How to add language parsers to SwiftTreeSitter

    main

    SwiftTreeSitter is a wrapper around the tree-sitter runtime API and does not include language grammars by default. To parse any language, you must combine the runtime with a specific parser project (e.g., tree-sitter-swift, tree-sitter-java).

    There are three primary ways to integrate parsers:

    1. Swift Package Manager (SPM): The most convenient method for Swift developers, provided the parser has SPM support.
    2. Manual Build: Building the parser (typically via Node.js) and manually interfacing it with Swift using C headers, ar for static libraries, and a Module.modulemap.
    3. Using Make: Using an adapted Makefile system designed to make the build process parser-generic.
  5. Perform syntax highlighting using SwiftTreeSitterLayer

    main

    SwiftTreeSitterLayer provides a high-level way to handle syntax highlighting, especially for documents with nested languages (like Markdown containing Swift code blocks).

    1. Use LanguageConfiguration to load language parsers and their bundled queries.
    2. Define a LanguageLayer.Configuration with a languageProvider closure to map language names (including injection names) to their respective configurations.
    3. Initialize a LanguageLayer with your root language and configuration.
    4. Use rootLayer.highlights(in:provider:) to retrieve named ranges for highlighting.
    // 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) {
    }

    """

    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)") }

  6. Integrate SwiftTreeSitter into your Swift project

    main

    You can add SwiftTreeSitter and SwiftTreeSitterLayer to your project using Swift Package Manager (SPM).

    • SwiftTreeSitter is a low-level target that closely matches the C runtime API.
    • SwiftTreeSitterLayer is a higher-level abstraction built on top of SwiftTreeSitter that supports nested languages, transparent querying, and asynchronous language resolution.
    dependencies: [
        .package(url: "https://github.com/tree-sitter/swift-tree-sitter"),
    ],
    targets: [
        .target(
            name: "MySwiftTreeSitterTarget",
            dependencies: [
                .product(name: "SwiftTreeSitter", package: "swift-tree-sitter", from: "0.9.0"),
            ]
        ),
        .target(
            name: "MySwiftTreeSitterLayerTarget",
            dependencies: [
                .product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
    
                // an optional product with additional features
                .product(name: "SwiftTreeSitterLayer", package: "swift-tree-sitter"),
            ]
        ),
    ]
  7. Perform syntax highlighting using SwiftTreeSitter

    main

    For simpler use cases where source text does not change or nesting is not required, you can use SwiftTreeSitter directly:

    1. Create a LanguageConfiguration for your language.
    2. Initialize a Parser and set its language.
    3. Parse the source string to get a Tree.
    4. Execute a query (e.g., the .highlights query) using a QueryCursor.
    5. Resolve the cursor with the source string and call .highlights() to get the results.
    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)
    }