Add runner::set_whitespace_mode() to keep runs of spaces inside a line - #171
Conversation
|
The PR has a good point; the implementation also looks good. I'm currently in doubt that the |
`clean_string` dropped any space whose neighbour was also a space, so `A B` reached the reader as `A B`. Every layer below it keeps the run: inklecate writes it into the JSON and the compiler carries it through. Only the final assembly of the line threw it away. It matters wherever ink drives a fixed-width display. Found on a 32-column thermal printer, where it flattened every piece of ASCII art in a story archive - the gutters drawing corridors and borders closed up, and 121 lines across five stories printed as a row of characters where a drawing should have been. Collapsing was doing one useful job, and it is kept: two fragments joined by glue often bring a trailing space and a leading space to the same join (`Knock ` + ` again?`), and that should read as one space. Move that to the join itself, in `basic_stream::get` and `get_alloc`, where the seam is actually known - so the interior of each fragment is left alone. Trailing whitespace before a newline is still trimmed, as the spec asks. Tests: an interior run reaches the output, and a seam still collapses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F52Q3KvwJXN7XLPDMQc4xF
d0316ba to
dc55113
Compare
The reference ink runtime collapses runs of spaces and tabs inside a line to one space (StoryState.CleanOutputWhitespace, "HTML style"), and inkjs does the same. inkcpp's existing collapse matches that, so it stays the default: with the option OFF the compiled code is master's. With INKCPP_KEEP_SPACE_RUNS ON, runs inside a line are kept, whitespace where glued fragments meet still reads as one character, and a run at the start or end of a line is dropped whole. The join check now treats tabs like spaces but never skips a newline. InteriorSpaces.cpp asserts the reference behaviour by default and the kept runs under the option. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks for the review. Your doubt about I've reworked the PR so the default doesn't change: the new behaviour is now behind a CMake option, Would you be OK with this as an opt-in? Our case is a fixed-width thermal printer, where collapsed runs flatten ASCII art. Two things I noticed but left alone:
|
|
Do you think it should be a compile-time option? Or should we allow when spawning threads/runner to pass an argument if inner spaces should be kept? For your case it is potentially potato-potato, but in general I think the runtime overhead is small enough to keep the flexibility. |
|
A runtime option works better, I agree. It also fixes the CI gap I mentioned: I'd suggest a setter on the runner rather than an argument when spawning it, the same way runner thread = story->new_runner(globals);
thread->set_keep_space_runs(true);Why a setter:
Internally the flag lives on the output stream, the two Two questions:
|
|
I favor a setter. An enum would be a better fit because of future options and better readability. The setter should also be available for C and Python. |
Replaces the INKCPP_KEEP_SPACE_RUNS compile-time option with a setter on the runner, as discussed in JBenda#171: whitespace_mode::collapse (the default, what the reference ink runtime does for lines) and keep_runs. The mode lives on the output stream, so it reaches everything a runner emits - lines, choice text and tags - and the two #ifdef blocks in get() and get_alloc() become plain branches. It is deliberately not part of a snapshot: a restored runner starts at the default, like a new one. Both modes are now covered by one default build, so CI exercises them: inkcpp_test/WhitespaceMode.cpp replaces InteriorSpaces.cpp. set_whitespace_mode() and set_rng_seed() are both exposed in the C API (InkWhitespaceMode) and in Python (WhitespaceMode), with tests for each; set_rng_seed was missing from both, which was not intentional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Done — a setter with an enum, in C++, C and Python, with
Two decisions I'd rather you sanity-check:
Verified locally: ctest 8/8 and pytest 14 passed, and formatted with clang-format 18.1.8 to match I pushed on top rather than force-pushing so your review thread keeps its anchor — the intermediate commit describing the compile-time option disappears on squash-merge. |
|
Nice job, I found an already existing error in the space collapse logic, which I would like to push in the same PR. Please pull the branch on top. |
Ink Proof ResultsThese results are obtained by running the Ink-Proof Testing Suite on the compiled binaries in this pull request.
|
What this adds
runner::set_whitespace_mode(), a runtime setting for how runs of whitespace inside a line are treated:collapse(default)keep_runsThe default is unchanged, so nothing moves for existing users.
Why
Text laid out for a fixed-width display — tables, ASCII art, anything aligned with gutters — loses its shape when runs collapse. inklecate writes the runs into the JSON (
"^A B") and the compiler carries them through; only the runtime's final cleanup removes them. We hit this driving a 32-column thermal printer.Scope
The mode lives on the output stream, so it covers everything a runner emits: lines, choice text and tags.
Knock+again?→Knock again?.Bindings
set_whitespace_mode()andset_rng_seed()are both exposed now:ink_runner_set_whitespace_mode()withInkWhitespaceMode, andink_runner_set_rng_seed()Runner.set_whitespace_mode()withWhitespaceMode, andRunner.set_rng_seed()set_rng_seed()was C++ only before.Tests
inkcpp_test/WhitespaceMode.cppcovers both modes in the default build, so CI exercises both without a second configuration.inkcpp_c/tests/WhitespaceMode.candinkcpp_python/tests/test_WhitespaceMode.pycover the bindings..pre-commit-config.yaml.Why an enum rather than a bool
There is a third behaviour worth room for: the reference runtime collapses runs in lines but does not collapse them in choice text, and neither value here matches that exactly. A future
ink_compatiblevalue is the natural home for it. Out of scope for this PR.🤖 Generated with Claude Code