Skip to content

Commit 91670f8

Browse files
authored
Merge pull request swiftlang#67 from akyrtzi/opt-raw-length
Optimize determination of a RawSyntax's SourceLength
2 parents 31ebb0e + d807416 commit 91670f8

16 files changed

+563
-249
lines changed

Sources/SwiftSyntax/AbsolutePosition.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
/// An absolute position in a source file as text - the absolute utf8Offset from
14-
/// the start, line, and column.
15-
public struct AbsolutePosition {
14+
/// the start of the file.
15+
public struct AbsolutePosition: Comparable {
1616
public let utf8Offset: Int
17-
public let line: Int
18-
public let column: Int
1917

20-
static let startOfFile = AbsolutePosition(line: 1, column: 1, utf8Offset: 0)
18+
static let startOfFile = AbsolutePosition(utf8Offset: 0)
2119

22-
public init(line: Int, column: Int, utf8Offset: Int) {
23-
self.line = line
24-
self.column = column
20+
public init(utf8Offset: Int) {
2521
self.utf8Offset = utf8Offset
2622
}
23+
24+
public static func <(lhs: AbsolutePosition, rhs: AbsolutePosition) -> Bool {
25+
return lhs.utf8Offset < rhs.utf8Offset
26+
}
2727
}

Sources/SwiftSyntax/Diagnostic.swift

-44
Original file line numberDiff line numberDiff line change
@@ -12,50 +12,6 @@
1212
// This file provides the Diagnostic, Note, and FixIt types.
1313
//===----------------------------------------------------------------------===//
1414

15-
import Foundation
16-
17-
/// Represents a source location in a Swift file.
18-
public struct SourceLocation: Codable {
19-
/// The line in the file where this location resides.
20-
public let line: Int
21-
22-
/// The UTF-8 byte offset from the beginning of the line where this location
23-
/// resides.
24-
public let column: Int
25-
26-
/// The UTF-8 byte offset into the file where this location resides.
27-
public let offset: Int
28-
29-
/// The file in which this location resides.
30-
public let file: String
31-
32-
public init(file: String, position: AbsolutePosition) {
33-
self.init(line: position.line, column: position.column,
34-
offset: position.utf8Offset, file: file)
35-
}
36-
37-
public init(line: Int, column: Int, offset: Int, file: String) {
38-
self.line = line
39-
self.column = column
40-
self.offset = offset
41-
self.file = file
42-
}
43-
}
44-
45-
/// Represents a start and end location in a Swift file.
46-
public struct SourceRange: Codable {
47-
/// The beginning location in the source range.
48-
public let start: SourceLocation
49-
50-
/// The beginning location in the source range.
51-
public let end: SourceLocation
52-
53-
public init(start: SourceLocation, end: SourceLocation) {
54-
self.start = start
55-
self.end = end
56-
}
57-
}
58-
5915
/// A FixIt represents a change to source code in order to "correct" a
6016
/// diagnostic.
6117
public enum FixIt: Codable {

Sources/SwiftSyntax/RawSyntax.swift

+57-45
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import Foundation
14-
1513
/// Box a value type into a reference type
1614
class Box<T> {
1715
let value: T
@@ -34,52 +32,21 @@ fileprivate enum RawSyntaxData {
3432
/// are immutable and can be freely shared between syntax nodes.
3533
final class RawSyntax {
3634
fileprivate let data: RawSyntaxData
35+
let totalLength: SourceLength
3736
let presence: SourcePresence
3837

39-
var _contentLength = LazyNonThreadSafeCache<Box<SourceLength>>()
40-
41-
/// The length of this node excluding its leading and trailing trivia
42-
var contentLength: SourceLength {
43-
return _contentLength.value({
44-
if isMissing {
45-
return Box(SourceLength.zero)
46-
}
47-
switch data {
48-
case .node(kind: _, layout: let layout):
49-
let firstElementIndex = layout.firstIndex(where: { $0 != nil })
50-
let lastElementIndex = layout.lastIndex(where: { $0 != nil })
51-
52-
var contentLength = SourceLength.zero
53-
for (offset, element) in layout.enumerated() {
54-
guard let element = element else {
55-
continue
56-
}
57-
// Skip the node's leading trivia
58-
if offset != firstElementIndex {
59-
contentLength += element.leadingTriviaLength
60-
}
61-
contentLength += element.contentLength
62-
// Skip the node's trailing trivia
63-
if offset != lastElementIndex {
64-
contentLength += element.trailingTriviaLength
65-
}
66-
}
67-
return Box(contentLength)
68-
case .token(kind: let kind, leadingTrivia: _, trailingTrivia: _):
69-
return Box(SourceLength(of: kind.text))
70-
}
71-
}).value
72-
}
73-
74-
init(kind: SyntaxKind, layout: [RawSyntax?], presence: SourcePresence) {
38+
init(kind: SyntaxKind, layout: [RawSyntax?], length: SourceLength,
39+
presence: SourcePresence) {
7540
self.data = .node(kind: kind, layout: layout)
41+
self.totalLength = length
7642
self.presence = presence
7743
}
7844

7945
init(kind: TokenKind, leadingTrivia: Trivia, trailingTrivia: Trivia,
80-
presence: SourcePresence) {
46+
length: SourceLength, presence: SourcePresence) {
8147
self.data = .token(kind: kind, leadingTrivia: leadingTrivia,
8248
trailingTrivia: trailingTrivia)
49+
self.totalLength = length
8350
self.presence = presence
8451
}
8552

@@ -129,7 +96,8 @@ final class RawSyntax {
12996
/// - Returns: A new RawSyntax `.node` with the provided kind and layout, with
13097
/// `.missing` source presence.
13198
static func missing(_ kind: SyntaxKind) -> RawSyntax {
132-
return RawSyntax(kind: kind, layout: [], presence: .missing)
99+
return RawSyntax(kind: kind, layout: [], length: SourceLength.zero,
100+
presence: .missing)
133101
}
134102

135103
/// Creates a RawSyntax token that's marked missing in the source with the
@@ -138,7 +106,8 @@ final class RawSyntax {
138106
/// - Returns: A new RawSyntax `.token` with the provided kind, no
139107
/// leading/trailing trivia, and `.missing` source presence.
140108
static func missingToken(_ kind: TokenKind) -> RawSyntax {
141-
return RawSyntax(kind: kind, leadingTrivia: [], trailingTrivia: [], presence: .missing)
109+
return RawSyntax(kind: kind, leadingTrivia: [], trailingTrivia: [],
110+
length: SourceLength.zero, presence: .missing)
142111
}
143112

144113
/// Returns a new RawSyntax node with the provided layout instead of the
@@ -149,7 +118,8 @@ final class RawSyntax {
149118
func replacingLayout(_ newLayout: [RawSyntax?]) -> RawSyntax {
150119
switch data {
151120
case let .node(kind, _):
152-
return RawSyntax(kind: kind, layout: newLayout, presence: presence)
121+
return .createAndCalcLength(kind: kind, layout: newLayout,
122+
presence: presence)
153123
case .token(_, _, _): return self
154124
}
155125
}
@@ -251,9 +221,51 @@ extension RawSyntax {
251221
return trailingTrivia?.sourceLength ?? .zero
252222
}
253223

254-
/// The length of this node including all of its trivia
255-
var totalLength: SourceLength {
256-
return leadingTriviaLength + contentLength + trailingTriviaLength
224+
/// The length of this node excluding its leading and trailing trivia.
225+
var contentLength: SourceLength {
226+
return totalLength - (leadingTriviaLength + trailingTriviaLength)
227+
}
228+
229+
/// Convenience function to create a RawSyntax when its byte length is not
230+
/// known in advance, e.g. it is programmatically constructed instead of
231+
/// created by the parser.
232+
///
233+
/// This is a separate function than in the initializer to make it more
234+
/// explicit and visible in the code for the instances where we don't have
235+
/// the length of the raw node already available.
236+
static func createAndCalcLength(kind: SyntaxKind, layout: [RawSyntax?],
237+
presence: SourcePresence) -> RawSyntax {
238+
let length: SourceLength
239+
if case .missing = presence {
240+
length = SourceLength.zero
241+
} else {
242+
var totalen = SourceLength.zero
243+
for child in layout {
244+
totalen += child?.totalLength ?? .zero
245+
}
246+
length = totalen
247+
}
248+
return .init(kind: kind, layout: layout, length: length, presence: presence)
249+
}
250+
251+
/// Convenience function to create a RawSyntax when its byte length is not
252+
/// known in advance, e.g. it is programmatically constructed instead of
253+
/// created by the parser.
254+
///
255+
/// This is a separate function than in the initializer to make it more
256+
/// explicit and visible in the code for the instances where we don't have
257+
/// the length of the raw node already available.
258+
static func createAndCalcLength(kind: TokenKind, leadingTrivia: Trivia,
259+
trailingTrivia: Trivia, presence: SourcePresence) -> RawSyntax {
260+
let length: SourceLength
261+
if case .missing = presence {
262+
length = SourceLength.zero
263+
} else {
264+
length = kind.sourceLength + leadingTrivia.sourceLength +
265+
trailingTrivia.sourceLength
266+
}
267+
return .init(kind: kind, leadingTrivia: leadingTrivia,
268+
trailingTrivia: trailingTrivia, length: length, presence: presence)
257269
}
258270
}
259271

Sources/SwiftSyntax/SourceLength.swift

+21-47
Original file line numberDiff line numberDiff line change
@@ -11,64 +11,45 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
/// The length a syntax node spans in the source code. From any AbsolutePosition
14-
/// you reach a node's end location by either adding its UTF-8 length or by
15-
/// inserting `lines` newlines and then moving `columns` columns to the right.
16-
public struct SourceLength {
17-
public let newlines: Int
18-
public let columnsAtLastLine: Int
14+
/// you reach a node's end location by adding its UTF-8 length.
15+
public struct SourceLength: Comparable {
1916
public let utf8Length: Int
2017

2118
/// Construct the source length of a given text
2219
public init(of text: String) {
23-
var newlines = 0
24-
var columnsAtLastLine = 0
25-
var utf8Length = 0
26-
for char in text {
27-
let charLength = String(char).utf8.count
28-
utf8Length += charLength
29-
switch char {
30-
case "\n", "\r\n", "\r":
31-
newlines += 1
32-
columnsAtLastLine = 0
33-
default:
34-
columnsAtLastLine += charLength
35-
}
36-
}
37-
self.newlines = newlines
38-
self.columnsAtLastLine = columnsAtLastLine
39-
self.utf8Length = utf8Length
20+
self.utf8Length = text.utf8.count
4021
}
4122

42-
public init(newlines: Int, columnsAtLastLine: Int, utf8Length: Int) {
43-
self.newlines = newlines
44-
self.columnsAtLastLine = columnsAtLastLine
23+
public init(utf8Length: Int) {
4524
self.utf8Length = utf8Length
4625
}
4726

4827
/// A zero-length source length
4928
public static let zero: SourceLength =
50-
SourceLength(newlines: 0, columnsAtLastLine: 0, utf8Length: 0)
29+
SourceLength(utf8Length: 0)
30+
31+
public static func <(lhs: SourceLength, rhs: SourceLength) -> Bool {
32+
return lhs.utf8Length < rhs.utf8Length
33+
}
5134

52-
/// Combine the length of two source length. Note that the addition is *not*
53-
/// commutative (3 columns + 1 line = 1 line but 1 line + 3 columns = 1 line
54-
/// and 3 columns)
35+
/// Combine the length of two source length.
5536
public static func +(lhs: SourceLength, rhs: SourceLength) -> SourceLength {
5637
let utf8Length = lhs.utf8Length + rhs.utf8Length
57-
let newlines = lhs.newlines + rhs.newlines
58-
let columnsAtLastLine: Int
59-
if rhs.newlines == 0 {
60-
columnsAtLastLine = lhs.columnsAtLastLine + rhs.columnsAtLastLine
61-
} else {
62-
columnsAtLastLine = rhs.columnsAtLastLine
63-
}
64-
return SourceLength(newlines: newlines,
65-
columnsAtLastLine: columnsAtLastLine,
66-
utf8Length: utf8Length)
38+
return SourceLength(utf8Length: utf8Length)
6739
}
6840

6941
public static func +=(lhs: inout SourceLength, rhs: SourceLength) {
7042
lhs = lhs + rhs
7143
}
44+
45+
public static func -(lhs: SourceLength, rhs: SourceLength) -> SourceLength {
46+
let utf8Length = lhs.utf8Length - rhs.utf8Length
47+
return SourceLength(utf8Length: utf8Length)
48+
}
49+
50+
public static func -=(lhs: inout SourceLength, rhs: SourceLength) {
51+
lhs = lhs - rhs
52+
}
7253
}
7354

7455
extension AbsolutePosition {
@@ -77,14 +58,7 @@ extension AbsolutePosition {
7758
public static func +(lhs: AbsolutePosition, rhs: SourceLength)
7859
-> AbsolutePosition {
7960
let utf8Offset = lhs.utf8Offset + rhs.utf8Length
80-
let line = lhs.line + rhs.newlines
81-
let column: Int
82-
if rhs.newlines == 0 {
83-
column = lhs.column + rhs.columnsAtLastLine
84-
} else {
85-
column = rhs.columnsAtLastLine + 1 // AbsolutePosition has 1-based columns
86-
}
87-
return AbsolutePosition(line: line, column: column, utf8Offset: utf8Offset)
61+
return AbsolutePosition(utf8Offset: utf8Offset)
8862
}
8963

9064
public static func +=(lhs: inout AbsolutePosition, rhs: SourceLength) {

0 commit comments

Comments
 (0)