-
Notifications
You must be signed in to change notification settings - Fork 127
/
Parser.swift
441 lines (399 loc) · 14.8 KB
/
Parser.swift
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
//
// Parser.swift
// Yams
//
// Created by Norio Nomura on 12/15/16.
// Copyright (c) 2016 Yams. All rights reserved.
//
// swiftlint:disable file_length
#if SWIFT_PACKAGE
@_implementationOnly import CYaml
#endif
import Foundation
/// Parse all YAML documents in a String
/// and produce corresponding Swift objects.
///
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
/// - parameter encoding: Parser.Encoding
///
/// - returns: YamlSequence<Any>
///
/// - throws: YamlError
public func load_all(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default,
_ encoding: Parser.Encoding = .default) throws -> YamlSequence<Any> {
let parser = try Parser(yaml: yaml, resolver: resolver, constructor: constructor, encoding: encoding)
return YamlSequence { try parser.nextRoot()?.any }
}
/// Parse the first YAML document in a String
/// and produce the corresponding Swift object.
///
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
/// - parameter encoding: Parser.Encoding
///
/// - returns: Any?
///
/// - throws: YamlError
public func load(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default,
_ encoding: Parser.Encoding = .default) throws -> Any? {
return try Parser(yaml: yaml, resolver: resolver, constructor: constructor, encoding: encoding).singleRoot()?.any
}
/// Parse all YAML documents in a String
/// and produce corresponding representation trees.
///
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
/// - parameter encoding: Parser.Encoding
///
/// - returns: YamlSequence<Node>
///
/// - throws: YamlError
public func compose_all(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default,
_ encoding: Parser.Encoding = .default) throws -> YamlSequence<Node> {
let parser = try Parser(yaml: yaml, resolver: resolver, constructor: constructor, encoding: encoding)
return YamlSequence(parser.nextRoot)
}
/// Parse the first YAML document in a String
/// and produce the corresponding representation tree.
///
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
/// - parameter encoding: Parser.Encoding
///
/// - returns: Node?
///
/// - throws: YamlError
public func compose(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default,
_ encoding: Parser.Encoding = .default) throws -> Node? {
return try Parser(yaml: yaml, resolver: resolver, constructor: constructor, encoding: encoding).singleRoot()
}
/// Sequence that holds an error.
public struct YamlSequence<T>: Sequence, IteratorProtocol {
/// This sequence's error, if any.
public private(set) var error: Swift.Error?
/// `Swift.Sequence.next()`.
public mutating func next() -> T? {
do {
return try closure()
} catch {
self.error = error
return nil
}
}
fileprivate init(_ closure: @escaping () throws -> T?) {
self.closure = closure
}
private let closure: () throws -> T?
}
/// Parses YAML strings.
public final class Parser {
/// YAML string.
public let yaml: String
/// Resolver.
public let resolver: Resolver
/// Constructor.
public let constructor: Constructor
/// Encoding
public enum Encoding: String {
/// Use `YAML_UTF8_ENCODING`
case utf8
/// Use `YAML_UTF16(BE|LE)_ENCODING`
case utf16
/// The default encoding, determined at run time based on the String type's native encoding.
/// This can be overridden by setting `YAMS_DEFAULT_ENCODING` to either `UTF8` or `UTF16`.
/// This value is case insensitive.
public static var `default`: Encoding = {
let key = "YAMS_DEFAULT_ENCODING"
if let yamsEncoding = ProcessInfo.processInfo.environment[key],
let encoding = Encoding(rawValue: yamsEncoding.lowercased()) {
print("""
`Parser.Encoding.default` was set to `\(encoding)` by the `\(key)` environment variable.
""")
return encoding
}
return key.utf8.withContiguousStorageIfAvailable({ _ in true }) != nil ? .utf8 : .utf16
}()
/// The equivalent `Swift.Encoding` value for `self`.
internal var swiftStringEncoding: String.Encoding {
switch self {
case .utf8:
return .utf8
case .utf16:
return .utf16
}
}
}
/// Encoding
public let encoding: Encoding
/// Set up a `Parser` with a `String` value as input.
///
/// - parameter string: YAML string.
/// - parameter resolver: Resolver, `.default` if omitted.
/// - parameter constructor: Constructor, `.default` if omitted.
/// - parameter encoding: Encoding, `.default` if omitted.
///
/// - throws: `YamlError`.
public init(yaml string: String,
resolver: Resolver = .default,
constructor: Constructor = .default,
encoding: Encoding = .default) throws {
yaml = string
self.resolver = resolver
self.constructor = constructor
self.encoding = encoding
yaml_parser_initialize(&parser)
switch encoding {
case .utf8:
yaml_parser_set_encoding(&parser, YAML_UTF8_ENCODING)
let utf8View = yaml.utf8
buffer = .utf8View(utf8View)
if try utf8View.withContiguousStorageIfAvailable(startParse(with:)) != nil {
// Attempt to parse with underlying UTF8 String encoding was successful, nothing further to do
} else {
// Fall back to using UTF8 slice
let utf8Slice = string.utf8CString.dropLast()
buffer = .utf8Slice(utf8Slice)
try utf8Slice.withUnsafeBytes(startParse(with:))
}
case .utf16:
// use native endianness
let isLittleEndian = 1 == 1.littleEndian
yaml_parser_set_encoding(&parser, isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING)
let encoding: String.Encoding = isLittleEndian ? .utf16LittleEndian : .utf16BigEndian
let data = yaml.data(using: encoding)!
buffer = .utf16(data)
try data.withUnsafeBytes(startParse(with:))
}
}
/// Set up a `Parser` with a `Data` value as input.
///
/// - parameter string: YAML Data encoded using the `encoding` encoding.
/// - parameter resolver: Resolver, `.default` if omitted.
/// - parameter constructor: Constructor, `.default` if omitted.
/// - parameter encoding: Encoding, `.default` if omitted.
///
/// - throws: `YamlError`.
public convenience init(yaml data: Data,
resolver: Resolver = .default,
constructor: Constructor = .default,
encoding: Encoding = .default) throws {
guard let yamlString = String(data: data, encoding: encoding.swiftStringEncoding) else {
throw YamlError.dataCouldNotBeDecoded(encoding: encoding.swiftStringEncoding)
}
try self.init(
yaml: yamlString,
resolver: resolver,
constructor: constructor,
encoding: encoding
)
}
deinit {
yaml_parser_delete(&parser)
}
/// Parse next document and return root Node.
///
/// - returns: next Node.
///
/// - throws: `YamlError`.
public func nextRoot() throws -> Node? {
guard !streamEndProduced, try parse().type != YAML_STREAM_END_EVENT else { return nil }
return try loadDocument()
}
/// Parses the document expecting a single root Node and returns it.
///
/// - returns: Single root Node.
///
/// - throws: `YamlError`.
public func singleRoot() throws -> Node? {
guard !streamEndProduced, try parse().type != YAML_STREAM_END_EVENT else { return nil }
let node = try loadDocument()
let event = try parse()
if event.type != YAML_STREAM_END_EVENT {
throw YamlError.composer(
context: YamlError.Context(text: "expected a single document in the stream",
mark: Mark(line: 1, column: 1)),
problem: "but found another document", event.startMark,
yaml: yaml
)
}
return node
}
// MARK: - Private Members
private var anchors = [String: Node]()
private var parser = yaml_parser_t()
private enum Buffer {
case utf8View(String.UTF8View)
case utf8Slice(ArraySlice<CChar>)
case utf16(Data)
}
private var buffer: Buffer
}
// MARK: Implementation Details
private extension Parser {
var streamEndProduced: Bool {
return parser.stream_end_produced != 0
}
func loadDocument() throws -> Node {
let node = try loadNode(from: parse())
try parse() // Drop YAML_DOCUMENT_END_EVENT
return node
}
func loadNode(from event: Event) throws -> Node {
switch event.type {
case YAML_ALIAS_EVENT:
return try loadAlias(from: event)
case YAML_SCALAR_EVENT:
return try loadScalar(from: event)
case YAML_SEQUENCE_START_EVENT:
return try loadSequence(from: event)
case YAML_MAPPING_START_EVENT:
return try loadMapping(from: event)
default:
fatalError("unreachable")
}
}
func startParse(with buffer: UnsafeRawBufferPointer) throws {
yaml_parser_set_input_string(&parser, buffer.baseAddress?.assumingMemoryBound(to: UInt8.self), buffer.count)
try parse() // Drop YAML_STREAM_START_EVENT
}
func startParse(with buffer: UnsafeBufferPointer<UInt8>) throws {
yaml_parser_set_input_string(&parser, buffer.baseAddress, buffer.count)
try parse() // Drop YAML_STREAM_START_EVENT
}
@discardableResult
func parse() throws -> Event {
let event = Event()
guard yaml_parser_parse(&parser, &event.event) == 1 else {
throw YamlError(from: parser, with: yaml)
}
return event
}
func loadAlias(from event: Event) throws -> Node {
guard let alias = event.aliasAnchor else {
fatalError("unreachable")
}
guard let node = anchors[alias] else {
throw YamlError.composer(context: nil,
problem: "found undefined alias", event.startMark,
yaml: yaml)
}
return node
}
func loadScalar(from event: Event) throws -> Node {
let node = Node.scalar(.init(event.scalarValue, tag(event.scalarTag), event.scalarStyle, event.startMark))
if let anchor = event.scalarAnchor {
anchors[anchor] = node
}
return node
}
func loadSequence(from firstEvent: Event) throws -> Node {
var array = [Node]()
var event = try parse()
while event.type != YAML_SEQUENCE_END_EVENT {
array.append(try loadNode(from: event))
event = try parse()
}
let node = Node.sequence(.init(array, tag(firstEvent.sequenceTag), event.sequenceStyle, firstEvent.startMark))
if let anchor = firstEvent.sequenceAnchor {
anchors[anchor] = node
}
return node
}
func loadMapping(from firstEvent: Event) throws -> Node {
var pairs = [(Node, Node)]()
var event = try parse()
while event.type != YAML_MAPPING_END_EVENT {
let key = try loadNode(from: event)
event = try parse()
let value = try loadNode(from: event)
pairs.append((key, value))
event = try parse()
}
let node = Node.mapping(.init(pairs, tag(firstEvent.mappingTag), event.mappingStyle, firstEvent.startMark))
if let anchor = firstEvent.mappingAnchor {
anchors[anchor] = node
}
return node
}
func tag(_ string: String?) -> Tag {
let tagName = string.map(Tag.Name.init(rawValue:)) ?? .implicit
return Tag(tagName, resolver, constructor)
}
}
/// Representation of `yaml_event_t`
private class Event {
var event = yaml_event_t()
deinit { yaml_event_delete(&event) }
var type: yaml_event_type_t {
return event.type
}
// alias
var aliasAnchor: String? {
return string(from: event.data.alias.anchor)
}
// scalar
var scalarAnchor: String? {
return string(from: event.data.scalar.anchor)
}
var scalarStyle: Node.Scalar.Style {
// swiftlint:disable:next force_unwrapping
return Node.Scalar.Style(rawValue: numericCast(event.data.scalar.style.rawValue))!
}
var scalarTag: String? {
if event.data.scalar.quoted_implicit == 1 {
return Tag.Name.str.rawValue
}
return string(from: event.data.scalar.tag)
}
var scalarValue: String {
// scalar may contain NULL characters
let buffer = UnsafeBufferPointer(start: event.data.scalar.value,
count: event.data.scalar.length)
// libYAML converts scalar characters into UTF8 if input is other than YAML_UTF8_ENCODING
return String(bytes: buffer, encoding: .utf8)!
}
// sequence
var sequenceAnchor: String? {
return string(from: event.data.sequence_start.anchor)
}
var sequenceStyle: Node.Sequence.Style {
// swiftlint:disable:next force_unwrapping
return Node.Sequence.Style(rawValue: numericCast(event.data.sequence_start.style.rawValue))!
}
var sequenceTag: String? {
return event.data.sequence_start.implicit != 0
? nil : string(from: event.data.sequence_start.tag)
}
// mapping
var mappingAnchor: String? {
return string(from: event.data.scalar.anchor)
}
var mappingStyle: Node.Mapping.Style {
// swiftlint:disable:next force_unwrapping
return Node.Mapping.Style(rawValue: numericCast(event.data.mapping_start.style.rawValue))!
}
var mappingTag: String? {
return event.data.mapping_start.implicit != 0
? nil : string(from: event.data.sequence_start.tag)
}
// start_mark
var startMark: Mark {
return Mark(line: event.start_mark.line + 1, column: event.start_mark.column + 1)
}
}
private func string(from pointer: UnsafePointer<UInt8>!) -> String? {
return String.decodeCString(pointer, as: UTF8.self, repairingInvalidCodeUnits: true)?.result
}