Skip to content

fix(pretokenize): split_corpus lost every newline when given a file path - #10

Merged
Mirdula18 merged 1 commit into
mainfrom
fix/split-corpus-newlines
Aug 1, 2026
Merged

fix(pretokenize): split_corpus lost every newline when given a file path#10
Mirdula18 merged 1 commit into
mainfrom
fix/split-corpus-newlines

Conversation

@Mirdula18

Copy link
Copy Markdown
Owner

The bug

split_corpus given a file path was not byte-lossless, in two ways that only affected that branch:

  1. str.splitlines() dropped every line terminator.
  2. read_text applied universal-newline translation, rewriting \r\n to \n.

The same text passed as a str or as an iterable of lines kept both.

text = 'alpha beta\ngamma delta\n'

split(text)            -> lossless: True
split_corpus([text])   -> lossless: True
split_corpus(text)     -> lossless: True
split_corpus(path)     -> b'alpha betagamma delta'   <-- newlines gone

A tokenizer trained through the file path never sees a line break, so it learns no merge spanning one and cannot represent indentation structure — worst for the source-code corpora this project targets.

Why it was invisible

split itself is lossless on every input, so test_pretokenize_lossless.py passed throughout while the corpus reaching the trainer was already damaged. The tests covered split, never split_corpus from a path.

The fix

Read with newline="" and split with keepends=True, so a path, a literal str, and an iterable of lines produce identical pre-tokens for identical text. Callers wanting \r\n normalized can do it themselves.

New tests cover LF, CRLF, no-trailing-newline, and bare-newline corpora across all three patterns, plus an explicit assertion that the three input forms agree.

316 tests pass; ruff and mypy clean.

Reading a corpus from disk went through str.splitlines(), which drops line
terminators, and through read_text, which rewrites \r\n to \n. The same text
passed as a str or as an iterable of lines kept both. So a tokenizer trained
from a file path never saw a line break, learned no merge spanning one, and
could not represent indentation structure -- worst for the source-code
corpora this project targets.

It stayed invisible because split() itself is lossless on every input: the
losslessness tests all passed while the corpus reaching the trainer was
already damaged.

Reads with newline="" and keepends=True, so a path, a literal str, and an
iterable of lines now yield identical pre-tokens for identical text. Tests
cover both, including CRLF, and assert the three input forms agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 05:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes split_corpus’s file-path handling so reading a corpus from disk preserves newline bytes and line terminators, making tokenization lossless and consistent with in-memory inputs (string / iterable-of-lines).

Changes:

  • Read corpus files with newline="" and split with splitlines(keepends=True) to preserve \n and \r\n.
  • Expand split_corpus docstring to document the newline-loss bug and rationale.
  • Add tests covering LF/CRLF/no-trailing-newline/bare-newlines and asserting equivalence across input forms.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/granule/pretokenize.py Updates file-path reading to preserve newline bytes and keep line terminators when splitting.
tests/test_pretokenize_lossless.py Adds regression tests for newline preservation and cross-input-form equivalence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 138 to 140
When *corpus* is a ``str`` or :class:`~pathlib.Path` pointing to an
existing file it is read line by line. Otherwise a ``str`` is treated
as literal text and split directly.
Comment on lines +117 to +120
from_path = b"".join(split_corpus(corpus, pattern)) # type: ignore[arg-type]
from_lines = b"".join(split_corpus(text.splitlines(keepends=True), pattern)) # type: ignore[arg-type]
from_str = b"".join(split_corpus([text], pattern)) # type: ignore[arg-type]
assert from_path == from_lines == from_str == text.encode("utf-8")

@Mirdula18 Mirdula18 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes Verified

@Mirdula18
Mirdula18 merged commit 5038496 into main Aug 1, 2026
3 checks passed
@Mirdula18
Mirdula18 deleted the fix/split-corpus-newlines branch August 1, 2026 07:31
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.

2 participants