Skip to content

fix: allow CACHE_SIZE=1 and preserve newlines in interpolated vars - #461

Draft
toddr-bot wants to merge 2 commits into
mainfrom
koan.toddr.bot/fix-cache-size-one
Draft

fix: allow CACHE_SIZE=1 and preserve newlines in interpolated vars#461
toddr-bot wants to merge 2 commits into
mainfrom
koan.toddr.bot/fix-cache-size-one

Conversation

@toddr-bot

@toddr-bot toddr-bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What

Two independent bug fixes in Provider and Parser, with tests.

Why

Provider (CACHE_SIZE=1): The _store() linked list eviction code crashes when HEAD == TAIL (single-slot cache) because it unconditionally dereferences $slot->[PREV], which is undef for the only node. The existing workaround silently clamped CACHE_SIZE=1 to 2 in _init(). This fixes the root cause and removes the clamp, allowing a legitimate configuration.

Parser (interpolate_text): Line 563 used tr/\n/ / instead of tr/\n// — a typo that replaces newlines with spaces in the stored $dir text for ${variable} references. The two adjacent counting lines (557, 568) and all lines in split_text() correctly use tr/\n// (count only, no modification). The corruption affects DEBUG output that displays original template source.

How

  • Provider: Guard $slot->[PREV] before dereferencing; compare $head != $slot to prevent self-referencing when reusing the sole cache slot. Remove the $size == 1 clamp from _init().
  • Parser: Change tr/\n/ / to tr/\n// on one line.

Testing

  • t/provider_cache.t: CACHE_SIZE 0, 1, 2, negative values, eviction cycling, reload, alternation, and content verification through Template.pm.
  • t/interp_newline.t: Verifies newline preservation in ${...} tokens, line number tracking, and end-to-end INTERPOLATE mode.
  • Full test suite passes (all existing tests unaffected).

Quality Report

Changes: 4 files changed, 234 insertions(+), 9 deletions(-)

Code scan: clean

Tests: passed (OK)

Branch hygiene: clean

Generated by Kōan

toddr-bot added 2 commits July 3, 2026 01:23
The _store() method crashed when evicting from a single-slot cache
because it unconditionally dereferenced $slot->[PREV] to disconnect
the tail node.  When HEAD == TAIL (only one slot), PREV is undef.

The existing workaround clamped CACHE_SIZE=1 to 2 in _init() with the
comment "it breaks things and the additional checking isn't worth it."

Fix the root cause instead:
- Guard the PREV dereference with a truthy check
- Prevent self-referencing when HEAD == TAIL by comparing $head != $slot
  before linking
- Remove the CACHE_SIZE=1 clamp from _init()

Add t/provider_cache.t covering CACHE_SIZE 0, 1, 2, negative values,
eviction cycling, and content verification through Template.pm.
In interpolate_text(), the line counter for ${variable} directives
used tr/\n/ / which both counts newlines AND replaces them with
spaces in $dir.  The adjacent lines for plain text (557) and
unmatched $ references (568) correctly use tr/\n// (count only).

The replacement corrupted the stored directive text — multiline
${variable} references had their newlines converted to spaces in
the token array, affecting DEBUG output that displays original
template source.

Change tr/\n/ / to tr/\n// to match the pattern used everywhere
else in split_text() and interpolate_text().

Add t/interp_newline.t verifying newline preservation, line number
tracking, and end-to-end INTERPOLATE mode rendering.
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