Skip to content

Commit 9c42e91

Browse files
committed
Use array for ParserState
Disadvantage is that we cannot reuse the Contiguous value array for the top-level item. (we could try to cache the struct in an ivar w/ a few hops). Another option would be to use a fixed size array or even a buffer-bptr to avoid the range checks.
1 parent 0f6b7ba commit 9c42e91

File tree

1 file changed

+33
-74
lines changed

1 file changed

+33
-74
lines changed

Sources/NIORedis/RESPParser.swift

Lines changed: 33 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,6 @@ public struct RESPParser {
3030

3131
private let allocator = ByteBufferAllocator()
3232

33-
@inline(__always)
34-
private mutating func decoded(value: RESPValue, yield: Yield) {
35-
if let arrayContext = arrayContext {
36-
_ = arrayContext.append(value: value)
37-
38-
if arrayContext.isDone {
39-
let v = RESPValue.array(arrayContext.values)
40-
41-
if let parent = arrayContext.parent {
42-
self.arrayContext = parent
43-
}
44-
else {
45-
self.arrayContext = nil
46-
if cachedParseContext == nil {
47-
cachedParseContext = arrayContext
48-
arrayContext.values = ContiguousArray()
49-
arrayContext.values.reserveCapacity(16)
50-
}
51-
}
52-
decoded(value: v, yield: yield)
53-
}
54-
}
55-
else {
56-
yield(value)
57-
}
58-
}
59-
6033
public mutating func feed(_ buffer: ByteBuffer, yield: Yield) throws {
6134
try buffer.withUnsafeReadableBytes { bp in
6235
let count = bp.count
@@ -146,11 +119,9 @@ public struct RESPParser {
146119
}
147120
else {
148121
if countValue > 0 {
149-
arrayContext =
150-
makeArrayParseContext(arrayContext, countValue)
122+
arrayContexts.append(ArrayParseContext(countValue))
151123
}
152124
else {
153-
// push an empty array
154125
decoded(value: .array([]), yield: yield)
155126
}
156127
}
@@ -260,24 +231,30 @@ public struct RESPParser {
260231
}
261232
}
262233

263-
// finish up.
264-
// TBD: I think this is not necessary anymore
265-
266-
if let arrayContext = arrayContext {
267-
if arrayContext.isDone {
268-
let values = arrayContext.values
269-
self.arrayContext = nil
270-
yield(.array(values))
271-
}
272-
else {
273-
// we leave the context around
274-
}
275-
}
234+
assert(!(arrayContexts.last?.isDone ?? false),
235+
"array context on stack which is done? \(arrayContexts)")
276236
}
277237

278238

279239
// MARK: - Parsing
280240

241+
@inline(__always)
242+
private mutating func decoded(value: RESPValue, yield: Yield) {
243+
if arrayContexts.isEmpty {
244+
return yield(value)
245+
}
246+
247+
let idx = arrayContexts.endIndex.advanced(by: -1)
248+
let isDone = arrayContexts[idx].append(value: value)
249+
250+
if isDone {
251+
let v = RESPValue.array(arrayContexts[idx].values)
252+
253+
_ = arrayContexts.popLast()
254+
decoded(value: v, yield: yield)
255+
}
256+
}
257+
281258
private enum ParserState {
282259
case protocolError
283260
case start
@@ -290,53 +267,35 @@ public struct RESPParser {
290267
case telnet
291268
}
292269

293-
@inline(__always)
294-
private mutating func makeArrayParseContext(_ parent: ArrayParseContext? = nil,
295-
_ count: Int) -> ArrayParseContext
296-
{
297-
if parent == nil && cachedParseContext != nil {
298-
let ctx = cachedParseContext!
299-
cachedParseContext = nil
300-
ctx.count = count
301-
ctx.values.reserveCapacity(count)
302-
return ctx
303-
}
304-
else {
305-
return ArrayParseContext(parent, count)
306-
}
307-
}
308-
private var cachedParseContext : ArrayParseContext? = nil
270+
private var arrayContexts = ContiguousArray<ArrayParseContext>()
309271

310-
final private class ArrayParseContext {
272+
private struct ArrayParseContext {
311273

312-
let parent : ArrayParseContext?
313-
var values = ContiguousArray<RESPValue>()
314-
var count : Int
274+
var values = ContiguousArray<RESPValue>()
275+
var expectedCount : Int
315276

316-
init(_ parent: ArrayParseContext?, _ count: Int) {
317-
self.parent = parent
318-
self.count = count
277+
init(_ expectedCount: Int) {
278+
self.expectedCount = expectedCount
279+
values.reserveCapacity(expectedCount + 1)
319280
}
320281

321-
var isDone : Bool { return count <= values.count }
322-
var isNested : Bool { return parent != nil }
282+
var isDone : Bool {
283+
@inline(__always) get { return expectedCount <= values.count }
284+
}
323285

324286
@inline(__always)
325-
func append(value v: RESPValue) -> Bool {
326-
assert(!isNested || !isDone,
327-
"attempt to add to a context which is not TL or done")
287+
mutating func append(value v: RESPValue) -> Bool {
288+
assert(!isDone, "attempt to add to a context which is not TL or done")
328289
values.append(v)
329290
return isDone
330291
}
331292
}
332293

333294
private var state = ParserState.start
334-
private var overflowSkipNL = false
335295
private var hadMinus = false
336296

337297
private var countValue = 0
298+
private var overflowSkipNL = false
338299
private var overflowBuffer : ByteBuffer?
339-
340-
private var arrayContext : ArrayParseContext?
341300

342301
}

0 commit comments

Comments
 (0)