Skip to content

Commit db24cb1

Browse files
authored
Improve parsing performance (#234)
* Convert some linear operations to constant time * Temporary test command for performance testing * Improve SplitArguments docs * Re-enable split arguments unit test * Restore repeat example
1 parent 365ca6a commit db24cb1

File tree

7 files changed

+247
-159
lines changed

7 files changed

+247
-159
lines changed

Sources/ArgumentParser/Parsing/ArgumentDecoder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class ArgumentDecoder: Decoder {
3535
self.usedOrigins = InputOrigin()
3636

3737
// Mark the terminator position(s) as used:
38-
values.elements.filter { $0.key == .terminator }.forEach {
38+
values.elements.values.filter { $0.key == .terminator }.forEach {
3939
usedOrigins.formUnion($0.inputOrigin)
4040
}
4141
}

Sources/ArgumentParser/Parsing/ArgumentSet.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,20 @@ extension ArgumentSet {
316316
}
317317
}
318318

319-
var result = ParsedValues(elements: [], originalInput: all.originalInput)
320-
var usedOrigins = InputOrigin()
319+
var result = ParsedValues(elements: [:], originalInput: all.originalInput)
320+
var allUsedOrigins = InputOrigin()
321321

322322
try setInitialValues(into: &result)
323323

324324
// Loop over all arguments:
325325
while let (origin, next) = inputArguments.popNext() {
326+
var usedOrigins = InputOrigin()
326327
defer {
327328
inputArguments.removeAll(in: usedOrigins)
329+
allUsedOrigins.formUnion(usedOrigins)
328330
}
329331

330-
switch next {
332+
switch next.value {
331333
case .value:
332334
// We'll parse positional values later.
333335
break
@@ -359,9 +361,9 @@ extension ArgumentSet {
359361
// We have parsed all non-positional values at this point.
360362
// Next: parse / consume the positional values.
361363
var unusedArguments = all
362-
unusedArguments.removeAll(in: usedOrigins)
364+
unusedArguments.removeAll(in: allUsedOrigins)
363365
try parsePositionalValues(from: unusedArguments, into: &result)
364-
366+
365367
return result
366368
}
367369
}
@@ -407,7 +409,7 @@ extension ArgumentSet {
407409
var argumentStack = unusedInput.elements.filter {
408410
$0.index.subIndex == .complete
409411
}.map {
410-
(InputOrigin.Element.argumentIndex($0.index), $0.element)
412+
(InputOrigin.Element.argumentIndex($0.index), $0)
411413
}[...]
412414

413415
guard !argumentStack.isEmpty else { return }

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ extension CommandParser {
9797
// We should have used up all arguments at this point:
9898
guard !split.containsNonTerminatorArguments else {
9999
// Check if one of the arguments is an unknown option
100-
for (index, element) in split.elements {
101-
if case .option(let argument) = element {
102-
throw ParserError.unknownOption(InputOrigin.Element.argumentIndex(index), argument.name)
100+
for element in split.elements {
101+
if case .option(let argument) = element.value {
102+
throw ParserError.unknownOption(InputOrigin.Element.argumentIndex(element.index), argument.name)
103103
}
104104
}
105105

@@ -296,12 +296,12 @@ extension CommandParser {
296296

297297
// Generate the argument set and parse the argument to find in the set
298298
let argset = ArgumentSet(current.element)
299-
let (_, parsedArgument) = try! parseIndividualArg(argToMatch, at: 0).first!
299+
let parsedArgument = try! parseIndividualArg(argToMatch, at: 0).first!
300300

301301
// Look up the specified argument and retrieve its custom completion function
302302
let completionFunction: ([String]) -> [String]
303303

304-
switch parsedArgument {
304+
switch parsedArgument.value {
305305
case .option(let parsed):
306306
guard let matchedArgument = argset.first(matching: parsed),
307307
case .custom(let f) = matchedArgument.completion.kind
@@ -362,7 +362,7 @@ extension CommandParser {
362362
extension SplitArguments {
363363
func contains(_ needle: Name) -> Bool {
364364
self.elements.contains {
365-
switch $0.element {
365+
switch $0.value {
366366
case .option(.name(let name)),
367367
.option(.nameWithValue(let name, _)):
368368
return name == needle
@@ -374,7 +374,7 @@ extension SplitArguments {
374374

375375
func contains(anyOf names: [Name]) -> Bool {
376376
self.elements.contains {
377-
switch $0.element {
377+
switch $0.value {
378378
case .option(.name(let name)),
379379
.option(.nameWithValue(let name, _)):
380380
return names.contains(name)

Sources/ArgumentParser/Parsing/ParsedValues.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ struct ParsedValues {
3636
}
3737

3838
/// These are the parsed key-value pairs.
39-
var elements: [Element] = []
39+
var elements: [InputKey: Element] = [:]
4040

4141
/// This is the *original* array of arguments that this was parsed from.
4242
///
@@ -50,20 +50,20 @@ extension ParsedValues {
5050
}
5151

5252
mutating func set(_ element: Element) {
53-
if let index = elements.firstIndex(where: { $0.key == element.key }) {
53+
if let e = elements[element.key] {
5454
// Merge the source values. We need to keep track
5555
// of any previous source indexes we have used for
5656
// this key.
57-
var e = element
58-
e.inputOrigin.formUnion(elements[index].inputOrigin)
59-
elements[index] = e
57+
var element = element
58+
element.inputOrigin.formUnion(e.inputOrigin)
59+
elements[element.key] = element
6060
} else {
61-
elements.append(element)
61+
elements[element.key] = element
6262
}
6363
}
6464

6565
func element(forKey key: InputKey) -> Element? {
66-
return elements.first(where: { $0.key == key })
66+
elements[key]
6767
}
6868

6969
mutating func update<A>(forKey key: InputKey, inputOrigin: InputOrigin, initial: A, closure: (inout A) -> Void) {

0 commit comments

Comments
 (0)