@@ -279,95 +279,77 @@ func normalizeCRLF(crMask, nlMask, nextNlMask uint64, validBits int) uint64 {
279279// processQuotesAndSeparators processes masks to track quote state and invalidate
280280// separators inside quoted regions. Detects escaped double quotes ("") including
281281// those spanning chunk boundaries.
282- func processQuotesAndSeparators (quoteMask , sepMask , newlineMask , nextQuoteMask uint64 , state * scanState ) (quoteMaskOut , sepMaskOut uint64 , hasDoubleQuote , boundaryDoubleQuote bool ) {
282+ func processQuotesAndSeparators (quoteMask , sepMask , nextQuoteMask uint64 , state * scanState ) (quoteMaskOut , sepMaskOut uint64 , hasDoubleQuote , boundaryDoubleQuote bool ) {
283283 quoteMaskOut = quoteMask
284- sepMaskOut = sepMask
285284 workQuote := quoteMask
286- workSep := sepMask
287- workNewline := newlineMask
288285 quoted := state .quoted
286+ initialQuoted := quoted
289287
290- for {
291- combined := workQuote | workSep | workNewline
292- if combined == 0 {
293- break
294- }
295-
296- pos := bits .TrailingZeros64 (combined )
288+ // Step 1: Process quotes to detect and remove double quotes
289+ for workQuote != 0 {
290+ pos := bits .TrailingZeros64 (workQuote )
297291 bit := uint64 (1 ) << pos
298292
299- switch {
300- case workQuote & bit != 0 :
301- if quoted != 0 {
302- // Inside quotes: check for escaped double quote
303- if pos == simdChunkSize - 1 && nextQuoteMask & 1 != 0 {
304- // Boundary double quote
305- quoteMaskOut &= ^ (uint64 (1 ) << (simdChunkSize - 1 ))
306- hasDoubleQuote = true
307- boundaryDoubleQuote = true
308- } else if pos < simdChunkSize - 1 && workQuote & (uint64 (1 )<< (pos + 1 )) != 0 {
309- // Adjacent double quote
310- quoteMaskOut &= ^ (uint64 (3 ) << pos )
311- hasDoubleQuote = true
312- workQuote &= ^ (uint64 (1 ) << (pos + 1 ))
313- } else {
314- // Closing quote
315- quoted = 0
316- }
293+ if quoted != 0 {
294+ // Inside quotes: check for escaped double quote
295+ if pos == simdChunkSize - 1 && nextQuoteMask & 1 != 0 {
296+ // Boundary double quote
297+ quoteMaskOut &^= uint64 (1 ) << (simdChunkSize - 1 )
298+ hasDoubleQuote = true
299+ boundaryDoubleQuote = true
300+ } else if pos < simdChunkSize - 1 && workQuote & (uint64 (1 )<< (pos + 1 )) != 0 {
301+ // Adjacent double quote
302+ quoteMaskOut &^= uint64 (3 ) << pos
303+ hasDoubleQuote = true
304+ workQuote &^= uint64 (1 ) << (pos + 1 )
317305 } else {
318- // Opening quote
319- quoted = ^ uint64 (0 )
320- }
321- workQuote &= ^ bit
322-
323- case workSep & bit != 0 :
324- if quoted != 0 {
325- sepMaskOut &= ^ bit
306+ // Closing quote
307+ quoted = 0
326308 }
327- workSep &= ^ bit
328-
329- default :
330- workNewline &= ^ bit
309+ } else {
310+ // Opening quote
311+ quoted = ^ uint64 (0 )
331312 }
313+ workQuote &^= bit
332314 }
333315
334316 state .quoted = quoted
317+
318+ // Step 2: Invalidate separators using prefix XOR on clean quote mask
319+ inQuote := quoteMaskOut
320+ inQuote ^= inQuote << 1
321+ inQuote ^= inQuote << 2
322+ inQuote ^= inQuote << 4
323+ inQuote ^= inQuote << 8
324+ inQuote ^= inQuote << 16
325+ inQuote ^= inQuote << 32
326+
327+ if initialQuoted != 0 {
328+ inQuote = ^ inQuote
329+ }
330+
331+ sepMaskOut = sepMask &^ inQuote
335332 return
336333}
337334
338335// invalidateNewlinesInQuotes removes newline bits that are inside quoted regions.
339336func invalidateNewlinesInQuotes (quoteMask , newlineMask uint64 , state * scanState ) uint64 {
340- quoted := state .quoted
341- result := newlineMask
342- workQuote := quoteMask
343- workNewline := newlineMask
344-
345- for {
346- combined := workQuote | workNewline
347- if combined == 0 {
348- break
349- }
350-
351- pos := bits .TrailingZeros64 (combined )
352- bit := uint64 (1 ) << pos
353-
354- if workQuote & bit != 0 {
355- if quoted != 0 {
356- quoted = 0
357- } else {
358- quoted = ^ uint64 (0 )
359- }
360- workQuote &= ^ bit
361- continue
362- }
363-
364- if quoted != 0 {
365- result &= ^ bit
366- }
367- workNewline &= ^ bit
337+ // Prefix XOR: inQuote[i] = 1 iff positions 0..i have odd number of quotes
338+ inQuote := quoteMask
339+ inQuote ^= inQuote << 1
340+ inQuote ^= inQuote << 2
341+ inQuote ^= inQuote << 4
342+ inQuote ^= inQuote << 8
343+ inQuote ^= inQuote << 16
344+ inQuote ^= inQuote << 32
345+
346+ // If we started inside a quoted region, invert the mask
347+ if state .quoted != 0 {
348+ inQuote = ^ inQuote
368349 }
369350
370- return result
351+ // Clear newline bits that are inside quoted regions
352+ return newlineMask &^ inQuote
371353}
372354
373355// =============================================================================
@@ -614,7 +596,7 @@ func processChunkWithQuotes(chunkIdx int, quoteMask, sepMask, newlineMask, nextQ
614596 initialQuoted := state .quoted
615597
616598 quoteMaskOut , sepMaskOut , hasDoubleQuote , boundaryDoubleQuote := processQuotesAndSeparators (
617- quoteMask , sepMask , newlineMask , nextQuoteMask , state ,
599+ quoteMask , sepMask , nextQuoteMask , state ,
618600 )
619601
620602 if boundaryDoubleQuote {
0 commit comments