Skip to content

Commit 24d7950

Browse files
committed
convert recursive function into a loop to avoid stack overflows
1 parent 99b5398 commit 24d7950

File tree

2 files changed

+41
-53
lines changed

2 files changed

+41
-53
lines changed

Sources/OpenAPIKitCore/URLTemplate/URLTemplate+Parsing.swift

+40-49
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,55 @@
77

88
extension URLTemplate {
99
internal static func scan(
10-
_ string: String,
11-
partialToken: PartialToken?,
12-
from remainder: Substring,
13-
addingTo tokens: [Component]
10+
_ string: String
1411
) throws -> [Component] {
15-
guard let next = remainder.first else {
16-
guard partialToken == nil || partialToken?.type == .constant else {
17-
throw ParsingError.unterminatedVariable(name: String(partialToken?.string ?? ""))
18-
}
19-
return tokens + tokenArray(from: partialToken)
20-
}
21-
let nextFirstIndex = remainder.index(remainder.startIndex, offsetBy: 1, limitedBy: remainder.endIndex)
12+
var tokens = [Component]()
13+
var remainder = string[...]
14+
var partialToken: PartialToken? = nil
15+
16+
while let next = remainder.first {
17+
let nextFirstIndex = remainder.index(remainder.startIndex, offsetBy: 1, limitedBy: remainder.endIndex)
2218

23-
switch (partialToken?.type, next) {
24-
case (nil, "{"),
25-
(.constant, "{"):
26-
guard let newFirstIndex = nextFirstIndex else {
27-
throw ParsingError.unterminatedVariable(name: "")
28-
}
29-
let newTokens = tokens + tokenArray(from: partialToken)
30-
return try scan(
31-
string,
32-
partialToken: .init(type: .variable, string: remainder[newFirstIndex..<newFirstIndex]),
33-
from: remainder.dropFirst(),
34-
addingTo: newTokens
35-
)
19+
switch (partialToken?.type, next) {
20+
case (nil, "{"),
21+
(.constant, "{"):
22+
guard let newFirstIndex = nextFirstIndex else {
23+
throw ParsingError.unterminatedVariable(name: "")
24+
}
25+
tokens += tokenArray(from: partialToken)
26+
partialToken = .init(type: .variable, string: remainder[newFirstIndex..<newFirstIndex])
27+
remainder = remainder.dropFirst()
3628

37-
case (.variable, "}"):
38-
let newTokens = tokens + tokenArray(from: partialToken)
39-
return try scan(string, partialToken: nil, from: remainder.dropFirst(), addingTo: newTokens)
29+
case (.variable, "}"):
30+
tokens += tokenArray(from: partialToken)
31+
partialToken = nil
32+
remainder = remainder.dropFirst()
4033

41-
case (nil, "}"),
42-
(.constant, "}"):
43-
throw ParsingError.variableEndedWithoutStarting(name: partialToken.map { String($0.string) } ?? "")
34+
case (nil, "}"),
35+
(.constant, "}"):
36+
throw ParsingError.variableEndedWithoutStarting(name: partialToken.map { String($0.string) } ?? "")
4437

45-
case (.variable, "{"):
46-
throw ParsingError.variableStartedWithinVariable(name: partialToken.map { String($0.string) } ?? "")
38+
case (.variable, "{"):
39+
throw ParsingError.variableStartedWithinVariable(name: partialToken.map { String($0.string) } ?? "")
4740

48-
case (nil, _):
49-
return try scan(
50-
string,
51-
partialToken: .init(type: .constant, string: remainder[remainder.startIndex...remainder.startIndex]),
52-
from: remainder.dropFirst(),
53-
addingTo: tokens
54-
)
41+
case (nil, _):
42+
partialToken = .init(type: .constant, string: remainder[remainder.startIndex...remainder.startIndex])
43+
remainder = remainder.dropFirst()
5544

56-
case (.constant, _),
57-
(.variable, _):
58-
guard nextFirstIndex != nil, let reifiedPartialToken = partialToken else {
59-
return tokens + tokenArray(from: partialToken)
45+
case (.constant, _),
46+
(.variable, _):
47+
guard nextFirstIndex != nil, let reifiedPartialToken = partialToken else {
48+
tokens += tokenArray(from: partialToken)
49+
continue
50+
}
51+
partialToken = reifiedPartialToken.advancingStringByOne(within: string)
52+
remainder = remainder.dropFirst()
6053
}
61-
return try scan(
62-
string,
63-
partialToken: reifiedPartialToken.advancingStringByOne(within: string),
64-
from: remainder.dropFirst(),
65-
addingTo: tokens
66-
)
6754
}
55+
guard partialToken == nil || partialToken?.type == .constant else {
56+
throw ParsingError.unterminatedVariable(name: String(partialToken?.string ?? ""))
57+
}
58+
return tokens + tokenArray(from: partialToken)
6859
}
6960

7061
internal static func tokenArray(from partial: PartialToken?) -> [Component] {

Sources/OpenAPIKitCore/URLTemplate/URLTemplate.swift

+1-4
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,7 @@ public struct URLTemplate: Hashable, RawRepresentable {
111111
public init(templateString: String) throws {
112112
rawValue = templateString
113113
components = try URLTemplate.scan(
114-
templateString,
115-
partialToken: nil,
116-
from: templateString[...],
117-
addingTo: []
114+
templateString
118115
)
119116
}
120117

0 commit comments

Comments
 (0)