-
Notifications
You must be signed in to change notification settings - Fork 75
Expand file tree
/
Copy pathReaderInference.swift
More file actions
206 lines (179 loc) · 8.74 KB
/
ReaderInference.swift
File metadata and controls
206 lines (179 loc) · 8.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
extension Delimiter.Scalars {
/// Closure accepting a scalar and returning a Boolean indicating whether the scalar (and subsquent unicode scalars from the input) form a delimiter.
/// - parameter scalar: The scalar that may start a delimiter.
/// - throws: `CSVError<CSVReader>` exclusively.
typealias Checker = (_ scalar: Unicode.Scalar) throws -> Bool
/// Creates a field delimiter identifier closure.
/// - parameter buffer: A unicode character buffer containing further characters to parse.
/// - parameter decoder: The instance providing the input `Unicode.Scalar`s.
/// - returns: A closure which given the targeted unicode character and the buffer and iterrator, returns a Boolean indicating whether there is a field delimiter.
func makeFieldMatcher(buffer: CSVReader.ScalarBuffer, decoder: @escaping CSVReader.ScalarDecoder) -> Self.Checker {
Self._makeMatcher(delimiter: self.field, buffer: buffer, decoder: decoder)
}
/// Creates a row delimiter identifir closure.
/// - parameter buffer: A unicode character buffer containing further characters to parse.
/// - parameter decoder: The instance providing the input `Unicode.Scalar`s.
/// - returns: A closure which given the targeted unicode character and the buffer and iterrator, returns a Boolean indicating whether there is a row delimiter.
func makeRowMatcher(buffer: CSVReader.ScalarBuffer, decoder: @escaping CSVReader.ScalarDecoder) -> Self.Checker {
guard self.row.count > 1 else {
return Self._makeMatcher(delimiter: self.row.first!, buffer: buffer, decoder: decoder)
}
let delimiters = self.row.sorted { $0.count < $1.count }
let maxScalars = delimiters.last!.count
// For optimization sake, a delimiter proofer is built for a single value scalar.
if maxScalars == 1 {
return { [dels = delimiters.map { $0.first! }] in dels.contains($0) }
// For optimization sake, a delimiter proofer is built for two unicode scalars.
} else if maxScalars == 2 {
return { [storage = Unmanaged.passUnretained(buffer), decoder,
singles = delimiters.compactMap { ($0.count == 1) ? $0.first! : nil },
doubles = delimiters.compactMap { ($0.count == 2) ? ($0[0], $0[1]) : nil }] (firstScalar) in
if singles.contains(firstScalar) { return true }
return try storage._withUnsafeGuaranteedRef {
guard let secondScalar = try $0.next() ?? decoder() else { return false }
for (first, second) in doubles where first==firstScalar && second==secondScalar { return true }
$0.preppend(scalar: secondScalar)
return false
}
}
} else {
return { [storage = Unmanaged.passUnretained(buffer), decoder] (firstScalar) in
try storage._withUnsafeGuaranteedRef {
var tmp: [Unicode.Scalar] = Array()
loop: for del in delimiters {
var iterator = del.makeIterator()
guard firstScalar == iterator.next().unsafelyUnwrapped else { continue loop }
var b = 0
while let delimiterScalar = iterator.next() {
let scalar: UnicodeScalar
if tmp.endIndex > b {
scalar = tmp[b]
} else if let decodedScalar = try $0.next() ?? decoder() {
scalar = decodedScalar
tmp.append(scalar)
} else {
break loop
}
guard scalar == delimiterScalar else { continue loop }
b &+= 1
}
return true
}
$0.preppend(scalars: tmp)
return false
}
}
}
}
}
private extension Delimiter.Scalars {
/// Creates a delimiter identifier closure.
/// - parameter delimiter: The value to be checked for.
/// - parameter buffer: A unicode character buffer containing further characters to parse.
/// - parameter decoder: The instance providing the input `Unicode.Scalar`s.
static func _makeMatcher(delimiter: [Unicode.Scalar], buffer: CSVReader.ScalarBuffer, decoder: @escaping CSVReader.ScalarDecoder) -> Self.Checker {
assert(!delimiter.isEmpty)
let count = delimiter.count
let first = delimiter.first.unsafelyUnwrapped
// For optimizations sake, a delimiter proofer is built for a single unicode scalar.
if count == 1 {
return { $0 == first }
}
let storage = Unmanaged.passUnretained(buffer)
let second = delimiter[1]
// For optimizations sake, a delimiter proofer is built for two unicode scalars.
if count == 2 {
return { [decoder] in
guard first == $0 else { return false }
return try storage._withUnsafeGuaranteedRef {
guard let scalar = try $0.next() ?? decoder() else { return false }
guard second == scalar else {
$0.preppend(scalar: scalar)
return false
}
return true
}
}
}
// For completion sake, a delimiter proofer is build for +2 unicode scalars. CSV files with multiscalar delimiters are very very rare.
let delimiterIterator = delimiter.makeIterator()
return { [decoder] in
var iterator = delimiterIterator
guard iterator.next().unsafelyUnwrapped == $0 else { return false }
return try storage._withUnsafeGuaranteedRef {
var tmp: [Unicode.Scalar] = Array()
tmp.reserveCapacity(count)
while let delimiterScalar = iterator.next() {
guard let scalar = try $0.next() ?? decoder() else {
storage._withUnsafeGuaranteedRef { $0.preppend(scalars: tmp) }
return false
}
tmp.append(scalar)
guard scalar == delimiterScalar else {
storage._withUnsafeGuaranteedRef { $0.preppend(scalars: tmp) }
return false
}
}
return true
}
}
}
}
extension CSVReader {
/// Tries to infer both the field and row delimiter from the raw data.
/// - parameter field: The field delimiter specified by the user.
/// - parameter row: The row delimiter specified by the user.
/// - parameter decoder: The instance providing the input `Unicode.Scalar`s.
/// - parameter buffer: Small buffer use to store `Unicode.Scalar` values that have been read from the input, but haven't yet been processed.
/// - throws: `CSVError<CSVReader>` exclusively.
/// - todo: Implement the field and row inferences.
static func inferDelimiters(field: Delimiter.Field, row: Delimiter.Row, decoder: ScalarDecoder, buffer: ScalarBuffer) throws -> Delimiter.Scalars {
let fieldDelimiter: Delimiter.Field
let rowDelimiter: Delimiter.Row
switch (field.isKnown, row.isKnown) {
case (true, true):
fieldDelimiter = field
rowDelimiter = row
case (false, true):
fieldDelimiter = try Self.inferFieldDelimiter(decoder: decoder, buffer: buffer)
rowDelimiter = row
default: throw Error._unsupportedInference()
}
guard let delimiters = Delimiter.Scalars(field: fieldDelimiter.scalars, row: rowDelimiter.scalars) else {
throw Error._invalidDelimiters(field: fieldDelimiter, row: rowDelimiter)
}
return delimiters
}
/// Tries to infer the field delimiter from the raw data.
/// - parameter decoder: The instance providing the input `Unicode.Scalar`s.
/// - parameter buffer: Small buffer use to store `Unicode.Scalar` values that have been read from the input, but haven't yet been processed.
/// - returns: The inferred `Delimiter.Field`.
static func inferFieldDelimiter(decoder: ScalarDecoder, buffer: ScalarBuffer) rethrows -> Delimiter.Field {
let sampleLength = 50
var tmp: [UnicodeScalar] = []
tmp.reserveCapacity(sampleLength)
while tmp.count < sampleLength {
guard let scalar = try buffer.next() ?? decoder() else { break }
tmp.append(scalar)
}
let detectedDialect = DialectDetector.detectDialect(stringScalars: tmp)
buffer.preppend(scalars: tmp)
return Delimiter.Field(unicodeScalarLiteral: detectedDialect.fieldDelimiter)
}
}
fileprivate extension CSVReader.Error {
/// Error raised when the field and row delimiters are the same.
/// - parameter delimiter: The indicated field and row delimiters.
static func _invalidDelimiters(field: Delimiter.Field, row: Delimiter.Row) -> CSVError<CSVReader> {
CSVError(.invalidConfiguration,
reason: "The field and row delimiters cannot be the same.",
help: "Set different delimiters for fields and rows.",
userInfo: ["Field delimiter": field.scalars, "Row delimiters": row.scalars])
}
/// Row delimiter inference is not yet implemented.
static func _unsupportedInference() -> CSVError<CSVReader> {
CSVError(.invalidConfiguration,
reason: "Row delimiter inference is not yet supported by this library",
help: "Specify a concrete delimiter or get in contact with the maintainer")
}
}