-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFormatSpecifierParser.swift
105 lines (87 loc) · 3.89 KB
/
FormatSpecifierParser.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
import Foundation
import Strix
extension Parser where T == FormatSpecifier {
public static var formatSpecifier: Parser<FormatSpecifier> {
return FormatSpecifierParserGenerator().formatSpecifier
}
public static var formatSpecifierContent: Parser<FormatSpecifier> {
return FormatSpecifierParserGenerator().formatSpecifierContent
}
}
private struct FormatSpecifierParserGenerator {
private typealias Index = FormatPlaceholder.Index
private typealias Flag = FormatPlaceholder.Flag
private typealias Width = FormatPlaceholder.Width
private typealias Precision = FormatPlaceholder.Precision
private typealias Length = FormatPlaceholder.Length
private typealias Conversion = FormatPlaceholder.Conversion
var formatSpecifier: Parser<FormatSpecifier> {
return .character("%") *> formatSpecifierContent
}
var formatSpecifierContent: Parser<FormatSpecifier> {
return percentSign <|> placeholder
}
private let percentSign: Parser<FormatSpecifier> = .character("%") *> .just(.percentSign)
private var placeholder: Parser<FormatSpecifier> {
let fields = Parser.tuple(
Parser.optional(index),
Parser.many(flag),
Parser.optional(width),
Parser.optional(precision),
Parser.optional(length),
conversion,
Parser.just(nil as String?))
return fields
.map(FormatPlaceholder.init)
.flatMap { [variableName] in
let expectsVariableName = $0.flags.contains(.hash) && $0.conversion == .object
return expectsVariableName ? variableName.map($0.withVariableName(_:)) : .just($0)
}
.map { .placeholder($0) }
}
private var index: Parser<Index> { .attempt(positiveInteger <* .character("$")) }
private var positiveInteger: Parser<Int> {
return decimalInteger.satisfying("positive integer", { $0 > 0 })
}
private let decimalInteger: Parser<Int> = {
return .number(options: .allowSign) { literal in
guard let number = literal.toValue(type: Int.self) else {
throw ParseError.generic(message: "\(literal.string) is outside the Int range")
}
return number
}
}()
private let flag: Parser<Flag> = {
let allFlagChars = Flag.allCases.map(\.rawValue)
return Parser.any(of: allFlagChars).map({ Flag(rawValue: $0)! })
}()
private var width: Parser<Width> {
let staticWidth = decimalInteger.map({ Width.static($0) })
let dynamicWidth = (.character("*") *> .optional(index)).map({ Width.dynamic($0) })
return staticWidth <|> dynamicWidth
}
private var precision: Parser<Precision> { .character(".") *> width }
private let length: Parser<Length> = {
let allLengthCases = Length.allCases.sorted { $0.rawValue > $1.rawValue }
let lengthParsers: [Parser<Length>] = allLengthCases.map { length in
return .string(length.rawValue) *> .just(length)
}
let label = "any string in [\(Length.allCases.map(\.rawValue).joined(separator: ", "))]"
return Parser.one(of: lengthParsers) <?> label
}()
private let conversion: Parser<Conversion> = {
let allConversionChars = Conversion.allCases.map(\.rawValue)
return Parser.any(of: allConversionChars).map({ Conversion(rawValue: $0)! })
}()
private let variableName: Parser<String> = {
let variableCharacters = Parser.one(of: [.asciiLetter, .decimalDigit, .character("_")])
return .skipped(by: .many(variableCharacters, minCount: 1)) <* .character("@")
}()
}
private extension FormatPlaceholder {
func withVariableName(_ variableName: String) -> FormatPlaceholder {
var result = self
result.variableName = variableName
return result
}
}