Land a character written past a short row's terminator

A grid row is a NUL-terminated string, and `putchar_at()` wrote the character,
advanced and terminated -- so `CHAR 1, 40, 1, "#"` on an otherwise empty row
stored the `#` at column 40 with `text[1][0]` still `'\0'`, and the render loop,
which stops at the terminator, drew nothing at all.

The silent nothing is the trap. The write succeeded, the cursor moved, the
stdout mirror showed the character, and only the window stayed blank -- so the
program looked right everywhere except where it mattered. The documented
truncation on the way *back* is fine and is unaffected.

`putchar_at()` now fills the gap with spaces before storing.

**Padded there rather than in `sink_moveto()`**, which TODO.md proposed: this way
`moveto` stays read-only -- the objection that entry raised against its own
suggestion -- and a row is only ever padded when a character actually arrives.

The pad fills from the terminator rather than replacing it. The buffer is not
cleared between rows, so replacing only the terminator would expose the tail of
whatever longer row used to be there: writing "ABCDEFGHIJ", truncating it to
"ABCX", then writing at column 7 must give "ABCX   Z" and not "ABCX FGZ".
tests/akgl_backends.c asserts all three cases, and the middle one keeps the
truncation pinned.

TODO.md section 6 item 32, struck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-02 00:13:42 -04:00
parent 8594d8471d
commit a1dcfcacf3
5 changed files with 89 additions and 7 deletions

View File

@@ -76,9 +76,23 @@ static void newline(akbasic_AkglSink *state)
* program that PRINTs a long string and then PRINTs again expects the second one
* to start on the row after the first one ended, and only the code that placed
* the characters knows which row that is.
*
* **A row is a NUL-terminated string, so a write past its end pads.** Without
* that, `CHAR 1, 40, 1, "#"` on an otherwise empty row stored the `#` at column
* 40 with `text[1][0]` still `'\0'` -- and the render loop, which stops at the
* terminator, drew nothing at all. The write succeeded, the cursor moved, the
* stdout mirror showed the character, and the window stayed blank. That silent
* nothing is the trap; the documented truncation on the way *back* is fine and
* is unaffected. TODO.md section 6 item 32.
*
* The padding is spaces rather than whatever was there: the buffer is not
* cleared between rows, so the gap holds the tail of some longer row that used
* to be here.
*/
static void putchar_at(akbasic_AkglSink *state, char c)
{
int col = 0;
if ( c == '\n' ) {
newline(state);
return;
@@ -86,6 +100,14 @@ static void putchar_at(akbasic_AkglSink *state, char c)
if ( state->cursorcol >= state->columns || state->cursorcol >= SINK_MAX_COLUMNS - 1 ) {
newline(state);
}
for ( col = 0; col < state->cursorcol; col++ ) {
if ( state->text[state->cursorrow][col] == '\0' ) {
break;
}
}
for ( ; col < state->cursorcol; col++ ) {
state->text[state->cursorrow][col] = ' ';
}
state->text[state->cursorrow][state->cursorcol] = c;
state->cursorcol += 1;
state->text[state->cursorrow][state->cursorcol] = '\0';