Skip to content

Release v5.5.0: move BOM handling into the parse orchestrator - #339

Merged
vincentlaucsb merged 6 commits into
masterfrom
claude/bom-stripping-refactor-plan-f82997
Oct 10, 2026
Merged

vincentlaucsb merged 6 commits into
masterfrom
claude/bom-stripping-refactor-plan-f82997

Conversation

@vincentlaucsb

@vincentlaucsb vincentlaucsb commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Release PR for v5.5.0. It contains the BOM stripping refactor and the version bump. The other changes since v5.4.0 are already on master and are listed below for the release notes.

BOM stripping refactor

Unicode BOM handling moves out of CSVParserCore and into CSVParseOrchestrator, where it runs once at the source-window boundary. The BOM ownership and source-offset invariants are documented in the Unicode BOM handling and source offsets section of include/internal/ARCHITECTURE.md.

  • On the first window, the orchestrator detects a BOM, rejects UTF-16/UTF-32 before any serial or speculative parser runs, and hands parsers a BOM-free view. The skipped bytes are reported back in CSVParseWindowResult, so source adapters keep working in source-byte offsets.
  • Format guessing strips the BOM itself, once per guess_format() call instead of once per candidate delimiter.
  • The parser core and the speculative chunk types no longer carry any BOM state. ParserChunkOptions is now (initial_state, source_start).

Bugs fixed

Bug Paths affected before
CSVReader::utf8_bom() returned false when the first stream window was parsed speculatively stream
The first row's raw_str() began with EF BB BF, and its byte_offset() was 0 instead of 3 mmap, stream
When the mmap head window completed no row, the re-read (from #337's fix) parsed the BOM as part of the first column name mmap

User-visible behavior changes

  • CSVRow::raw_str() for the first row of a UTF-8 BOM file no longer includes the BOM.
  • CSVRow::byte_offset() for that row is now 3, which is where the row starts in the source. Offsets of later rows are unchanged.
  • utf8_bom() is now correct for speculative stream input.

Field values were already correct before this change and are unchanged.

Note on #337 interaction

MmapParser::next() used to decide whether the head window "completed a row" by comparing positions. A skipped BOM now advances the position without completing a row, so the check uses the new CSVParseWindowResult::completed_row() instead.

Other changes since v5.4.0 (already on master)

Version bump

  • include/csv.hpp and pyproject.toml move from 5.4.0 to 5.5.0.

Testing

New [read_unicode_bom] tests cover the mmap and stream paths:

  • UTF-8 BOM stripping with guessed and explicit formats
  • a double BOM, where only the first is stripped
  • UTF-16 LE/BE and UTF-32 LE/BE rejection
  • empty input, a BOM-only file, and a BOM followed by one unterminated row
  • the first row's raw_str() and byte_offset()
  • a 600K-row BOM file crossing chunk boundaries, parsed serially and with speculative parsing forced on
  • guess_format() on BOM and UTF-16 files

There is also a BOM variant of the [issue_337] long-header test.

Before the refactor, these tests failed on the three bugs listed above. Now they pass.

Local runs on Windows with MSVC (VS 2026, x64, Release):

  • C++20 with threads on: ctest passes for all 3 targets
  • C++11 with threads on: ctest passes for all 3 targets. I compiled and linked the two single_include_test translation units against the freshly generated v5.5.0 single header in a standalone project. The in-tree single_include_test target hits the known MSBuild project-discovery issue on Windows, which is why CI skips it there.
  • C++20 with CSV_ENABLE_THREADS=OFF: ctest passes for all 3 targets

To catch stale call sites when removing the scan_bom parameter, I temporarily deleted the ParserChunkOptions(ParserDFAState, bool) overloads. That surfaced seven test calls like ParserChunkOptions(state, false), which would otherwise have compiled silently as source_start = 0.

🤖 Generated with Claude Code

Review validation

An Astra review found no runtime correctness or regression issues. Two inaccurate window/reread comments were corrected, and the standalone BOM refactor history was consolidated into the internal architecture document.

MSVC Release validation after the cleanup:

  • C++20 with threads on: all 3 CTest targets pass.
  • Focused [read_unicode_bom],[issue_337] regressions: 8 test cases and 9,620,127 assertions pass.
  • The existing two-translation-unit single-header smoke test compiles and links in a standalone CMake project configured for C++11, using the freshly generated header, and runs successfully on a UTF-8 BOM fixture.

vincentlaucsb and others added 5 commits October 10, 2026 12:17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CSVParseOrchestrator now scans the first source window for a Unicode BOM,
rejects UTF-16/UTF-32 before any serial or speculative parser runs, and
hands parsers a BOM-free view. The skipped bytes are reported back through
CSVParseWindowResult so source adapters keep speaking in source offsets.
Format guessing strips the BOM itself once per guess.

This fixes:
- utf8_bom() returning false for stream input whose first window went
  through the speculative parser (only a worker core saw the BOM).
- The first row's raw_str() including the BOM bytes and byte_offset()
  pointing at the BOM instead of the row.
- The BOM leaking into the first column name when the mmap head window
  completes no row and is re-read from offset 0 (exposed by #337).

MmapParser now asks the parse result whether the head completed a row
instead of comparing positions, since a skipped BOM advances mmap_pos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CSVParseOrchestrator and format guessing now own BOM handling, so the core
no longer needs per-instance scan state. Remove ParserChunkOptions::scan_bom,
the core's utf8_bom()/strip_unicode_bom() and their state, and the scan_bom
flags on speculative chunks. ParserChunkOptions is now
(initial_state, source_start).

Stale two-argument ParserChunkOptions(state, false) calls in tests would
otherwise have compiled silently as source_start = 0; they were found by
temporarily deleting the bool overloads.

Update ARCHITECTURE.md, JOURNEY_OF_A_CSV_FIELD.md, and the refactor page to
reflect that the orchestrator owns BOM handling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.79%. Comparing base (796611f) to head (5c58035).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #339      +/-   ##
==========================================
+ Coverage   91.61%   91.79%   +0.18%     
==========================================
  Files          61       61              
  Lines        3780     3780              
  Branches     1101     1100       -1     
==========================================
+ Hits         3463     3470       +7     
+ Misses        110      104       -6     
+ Partials      207      206       -1     
Flag Coverage Δ
cpp 91.67% <100.00%> (+0.19%) ⬆️
python 93.64% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vincentlaucsb
vincentlaucsb merged commit 642df68 into master Oct 10, 2026
35 checks passed
@vincentlaucsb
vincentlaucsb deleted the claude/bom-stripping-refactor-plan-f82997 branch October 10, 2026 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant