mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
AbsolutePosition being a mutable reference type easily leads to bugs where an AbsolutePosition is modified. Making it immutable eliminates this issue. Furthermore, the introduction of SourceLength should allow easier manipulation of AbsolutePositions on the client side. We still cannot make AbsolutePosition a value type since it is used inside AtomicCache, but the immutability gives the same safety.
244 lines
9.4 KiB
Swift
244 lines
9.4 KiB
Swift
//===-------------------- SyntaxData.swift - Syntax Data ------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Foundation
|
|
|
|
/// A unique identifier for a node in the tree.
|
|
/// Currently, this is an index path from the current node to the root of the
|
|
/// tree. It's an implementation detail and shouldn't be
|
|
/// exposed to clients.
|
|
typealias NodeIdentifier = [Int]
|
|
|
|
/// SyntaxData is the underlying storage for each Syntax node.
|
|
/// It's modelled as an array that stores and caches a SyntaxData for each raw
|
|
/// syntax node in its layout. It is up to the specific Syntax nodes to maintain
|
|
/// the correct layout of their SyntaxData backing stores.
|
|
///
|
|
/// SyntaxData is an implementation detail, and should not be exposed to clients
|
|
/// of libSyntax.
|
|
///
|
|
/// The following relationships are preserved:
|
|
/// parent.cachedChild(at: indexInParent) === self
|
|
/// raw.layout.count == childCaches.count
|
|
/// pathToRoot.first === indexInParent
|
|
final class SyntaxData: Equatable {
|
|
|
|
let raw: RawSyntax
|
|
let indexInParent: Int
|
|
weak var parent: SyntaxData?
|
|
|
|
let childCaches: [AtomicCache<SyntaxData>]
|
|
|
|
let positionCache: AtomicCache<AbsolutePosition>
|
|
|
|
fileprivate func calculatePosition() -> AbsolutePosition {
|
|
guard let parent = parent else {
|
|
// If this node is SourceFileSyntax, its location is the start of the file.
|
|
return AbsolutePosition.startOfFile
|
|
}
|
|
|
|
// If the node is the first child of its parent, the location is same with
|
|
// the parent's location.
|
|
guard indexInParent != 0 else { return parent.position }
|
|
|
|
// Otherwise, the location is same with the previous sibling's location
|
|
// adding the stride of the sibling.
|
|
for idx in (0..<indexInParent).reversed() {
|
|
if let sibling = parent.cachedChild(at: idx) {
|
|
return sibling.position + sibling.raw.totalLength
|
|
}
|
|
}
|
|
return parent.position
|
|
}
|
|
|
|
/// The position of the start of this node's leading trivia
|
|
var position: AbsolutePosition {
|
|
return positionCache.value { return calculatePosition() }
|
|
}
|
|
|
|
/// The position of the start of this node's content, skipping its trivia
|
|
var positionAfterSkippingLeadingTrivia: AbsolutePosition {
|
|
return position + raw.leadingTriviaLength
|
|
}
|
|
|
|
/// The end position of this node's content, excluding its trivia
|
|
var endPosition: AbsolutePosition {
|
|
return positionAfterSkippingLeadingTrivia + raw.contentLength
|
|
}
|
|
|
|
/// The end position of this node's trivia
|
|
var endPositionAfterTrailingTrivia: AbsolutePosition {
|
|
return endPosition + raw.trailingTriviaLength
|
|
}
|
|
|
|
/// Creates a SyntaxData with the provided raw syntax, pointing to the
|
|
/// provided index, in the provided parent.
|
|
/// - Parameters:
|
|
/// - raw: The raw syntax underlying this node.
|
|
/// - indexInParent: The index in the parent's layout where this node will
|
|
/// reside.
|
|
/// - parent: The parent of this node, or `nil` if this node is the root.
|
|
required init(raw: RawSyntax, indexInParent: Int = 0,
|
|
parent: SyntaxData? = nil) {
|
|
self.raw = raw
|
|
self.indexInParent = indexInParent
|
|
self.parent = parent
|
|
self.childCaches = raw.layout.map { _ in AtomicCache<SyntaxData>() }
|
|
self.positionCache = AtomicCache<AbsolutePosition>()
|
|
}
|
|
|
|
/// The index path from this node to the root. This can be used to uniquely
|
|
/// identify this node in the tree.
|
|
lazy private(set) var pathToRoot: NodeIdentifier = {
|
|
var path = [Int]()
|
|
var node = self
|
|
while let parent = node.parent {
|
|
path.append(node.indexInParent)
|
|
node = parent
|
|
}
|
|
return path
|
|
}()
|
|
|
|
/// Returns the child data at the provided index in this data's layout.
|
|
/// This child is cached and will be used in subsequent accesses.
|
|
/// - Note: This function traps if the index is out of the bounds of the
|
|
/// data's layout.
|
|
///
|
|
/// - Parameter index: The index to create and cache.
|
|
/// - Returns: The child's data at the provided index.
|
|
func cachedChild(at index: Int) -> SyntaxData? {
|
|
if raw.layout[index] == nil { return nil }
|
|
return childCaches[index].value { realizeChild(index) }
|
|
}
|
|
|
|
/// Returns the child data at the provided cursor in this data's layout.
|
|
/// This child is cached and will be used in subsequent accesses.
|
|
/// - Note: This function traps if the cursor is out of the bounds of the
|
|
/// data's layout.
|
|
///
|
|
/// - Parameter cursor: The cursor to create and cache.
|
|
/// - Returns: The child's data at the provided cursor.
|
|
func cachedChild<CursorType: RawRepresentable>(
|
|
at cursor: CursorType) -> SyntaxData?
|
|
where CursorType.RawValue == Int {
|
|
return cachedChild(at: cursor.rawValue)
|
|
}
|
|
|
|
/// Walks up the provided node's parent tree looking for the receiver.
|
|
/// - parameter data: The potential child data.
|
|
/// - returns: `true` if the receiver exists in the parent chain of the
|
|
/// provided data.
|
|
/// - seealso: isDescendent(of:)
|
|
func isAncestor(of data: SyntaxData) -> Bool {
|
|
return data.isDescendent(of: self)
|
|
}
|
|
|
|
/// Walks up the receiver's parent tree looking for the provided node.
|
|
/// - parameter data: The potential ancestor data.
|
|
/// - returns: `true` if the data exists in the parent chain of the receiver.
|
|
/// - seealso: isAncestor(of:)
|
|
func isDescendent(of data: SyntaxData) -> Bool {
|
|
if data == self { return true }
|
|
var node = self
|
|
while let parent = node.parent {
|
|
if parent == data { return true }
|
|
node = parent
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Creates a copy of `self` and recursively creates `SyntaxData` nodes up to
|
|
/// the root.
|
|
/// - parameter newRaw: The new RawSyntax that will back the new `Data`
|
|
/// - returns: A tuple of both the new root node and the new data with the raw
|
|
/// layout replaced.
|
|
func replacingSelf(
|
|
_ newRaw: RawSyntax) -> (root: SyntaxData, newValue: SyntaxData) {
|
|
// If we have a parent already, then ask our current parent to copy itself
|
|
// recursively up to the root.
|
|
if let parent = parent {
|
|
let (root, newParent) = parent.replacingChild(newRaw, at: indexInParent)
|
|
let newMe = newParent.cachedChild(at: indexInParent)!
|
|
return (root: root, newValue: newMe)
|
|
} else {
|
|
// Otherwise, we're already the root, so return the new data as both the
|
|
// new root and the new data.
|
|
let newMe = SyntaxData(raw: newRaw, indexInParent: indexInParent,
|
|
parent: nil)
|
|
return (root: newMe, newValue: newMe)
|
|
}
|
|
}
|
|
|
|
/// Creates a copy of `self` with the child at the provided index replaced
|
|
/// with a new SyntaxData containing the raw syntax provided.
|
|
///
|
|
/// - Parameters:
|
|
/// - child: The raw syntax for the new child to replace.
|
|
/// - index: The index pointing to where in the raw layout to place this
|
|
/// child.
|
|
/// - Returns: The new root node created by this operation, and the new child
|
|
/// syntax data.
|
|
/// - SeeAlso: replacingSelf(_:)
|
|
func replacingChild(_ child: RawSyntax,
|
|
at index: Int) -> (root: SyntaxData, newValue: SyntaxData) {
|
|
let newRaw = raw.replacingChild(index, with: child)
|
|
return replacingSelf(newRaw)
|
|
}
|
|
|
|
/// Creates a copy of `self` with the child at the provided cursor replaced
|
|
/// with a new SyntaxData containing the raw syntax provided.
|
|
///
|
|
/// - Parameters:
|
|
/// - child: The raw syntax for the new child to replace.
|
|
/// - cursor: A cursor that points to the index of the child you wish to
|
|
/// replace
|
|
/// - Returns: The new root node created by this operation, and the new child
|
|
/// syntax data.
|
|
/// - SeeAlso: replacingSelf(_:)
|
|
func replacingChild<CursorType: RawRepresentable>(_ child: RawSyntax,
|
|
at cursor: CursorType) -> (root: SyntaxData, newValue: SyntaxData)
|
|
where CursorType.RawValue == Int {
|
|
return replacingChild(child, at: cursor.rawValue)
|
|
}
|
|
|
|
/// Creates the child's syntax data for the provided cursor.
|
|
///
|
|
/// - Parameter cursor: The cursor pointing into the raw syntax's layout for
|
|
/// the child you're creating.
|
|
/// - Returns: A new SyntaxData for the specific child you're
|
|
/// creating, whose parent is pointing to self.
|
|
func realizeChild<CursorType: RawRepresentable>(
|
|
_ cursor: CursorType) -> SyntaxData
|
|
where CursorType.RawValue == Int {
|
|
return realizeChild(cursor.rawValue)
|
|
}
|
|
|
|
/// Creates the child's syntax data for the provided index.
|
|
///
|
|
/// - Parameter cursor: The cursor pointing into the raw syntax's layout for
|
|
/// the child you're creating.
|
|
/// - Returns: A new SyntaxData for the specific child you're
|
|
/// creating, whose parent is pointing to self.
|
|
func realizeChild(_ index: Int) -> SyntaxData {
|
|
return SyntaxData(raw: raw.layout[index]!,
|
|
indexInParent: index,
|
|
parent: self)
|
|
}
|
|
|
|
/// Tells whether two SyntaxData nodes have the same identity.
|
|
/// This is not structural equality.
|
|
/// - Returns: True if both datas are exactly the same.
|
|
static func ==(lhs: SyntaxData, rhs: SyntaxData) -> Bool {
|
|
return lhs === rhs
|
|
}
|
|
}
|