proven_c_lib-v0.2.0
A MINOR release: new public API, nothing removed. UTF-16 text gets a way in and out and UTF-8
shows correctly on a Windows console (B-039); the view vocabulary - split, trim, affixes,
find_last, contains, ordering (RFC-0005, B-018 to B-022); an allocator wrapper that catches the
wrong allocator at the call (B-040); Windows symlinks and the 4 GiB entropy boundary measured and
fixed (B-033); reproducible manual PDFs. Existing behaviour changes only where it was wrong: on a
Windows console, for Windows symlinks, and in proven_time_u16_fmt with non-ASCII locales.
Added
-
utf.h: strict UTF-8 <-> UTF-16 transcoding. Measuring (proven_utf8_to_utf16_size,
proven_utf16_to_utf8_size), fixed-capacity all-or-nothing (proven_utf8_to_utf16,
proven_utf16_to_utf8), partial for text read in pieces (proven_utf8_to_utf16_partial,
proven_utf16_to_utf8_partial, reportingproven_utf_step_t), and growable all-or-nothing
(proven_utf8_append_to_u16str,proven_utf16_append_to_u8str). Malformed input is always
PROVEN_ERR_INVALID_ENCODING- overlongs, encoded surrogates, values above U+10FFFF, stray
continuations, unpaired surrogates - and nothing is repaired. Input cut mid-character is
PROVEN_ERR_NEED_MOREin the partial forms, malformed in the whole forms. -
u16 text through the formatter.
proven_arg_u16, andPROVEN_ARGon a
proven_u16str_view_t, render UTF-8 into every formatter sink:proven_println,
proven_eprintln,proven_fprintlninto any writer,proven_u8str_append_fmt*. Width counts
UTF-8 bytes as for a u8 view; an unpaired surrogate fails the format. -
u16 text through writers and readers (
stream.h).proven_writer_write_u16writes UTF-8,
UTF-16LE or UTF-16BE (proven_text_encoding_t), validated before anything is written;
proven_writer_write_bomwrites a byte order mark only on request.proven_u16_reader_t
(proven_u16_reader_init,_read_line,_read) decodes any of the three encodings from any
reader into a caller-owned buffer of code units, withPROVEN_TEXT_AUTOchoosing by BOM,
carrying a character split across reads, and keeping the byte line reader's newline, full-buffer
and last-line rules. -
sysio u16 line input:
proven_sysio_u16_lines_open,proven_sysio_stdin_u16_lines,
proven_sysio_read_u16_line, withproven_sysio_u16_lines_t.proven_result_u16str_view_tin
u16str.h. -
PAL:
proven_sys_io_is_console,proven_sys_io_console_write_u16,
proven_sys_io_console_read_u16(Windows; POSIX answers "not a console" / unsupported). -
Manual: chapter 3 "Converting between UTF-8 and UTF-16", chapter 5 "UTF-16 text in and out,
and the Windows console", both editions, with runnable examplesex_03_utfandex_05_u16_io. -
b039-console-check.candbuild-b039-check.sh: a native Windows check that
makes its own console in code page 949 and verifies output by reading the screen buffer back
and input by injecting key events. -
alloc_check.h: an allocator that knows its own blocks (B-040).proven_alloc_check_wrap
puts a checker in front of any allocator and records the blocks it hands out in caller-supplied
memory; a foreign free, a double free, a realloc of a foreign block or with the wrong old size or
alignment, and an allocation past the record are refused withproven_panicat the call, and the
refused pointer never reaches the inner allocator.proven_alloc_checkedwraps only where
PROVEN_ALLOC_CHECKis defined (before the first proven header, e.g.-D) and is otherwise the
identity - it is a testing and debugging tool, and the lookup is linear.proven_alloc_check_owns,
proven_alloc_check_live(a leak check). Teststest_unit_alloc_check,test_unit_alloc_check_on;
manual chapter 2 section 7 withex_02_alloc_check, both editions. -
The view vocabulary (RFC-0005; B-018 to B-022). In
u8str.h, all pure and non-allocating,
ill-formed views treated as empty, every empty result{NULL, 0}:
proven_u8str_view_split/_split_nextwithproven_u8str_view_split_t(n separators yield
n + 1 fields; an empty separator yields the input once; the iterator is copyable);
proven_u8str_view_trim,_trim_start,_trim_end(exactly six ASCII whitespace bytes);
proven_u8str_view_remove_prefix/_remove_suffix(unchanged when absent);
proven_u8str_view_find_last(last start position, overlaps counted; size for an empty needle;
byte scan / backward Shift-Or / repeated forward search by needle length) and
proven_u8str_view_contains;proven_u8str_view_cmp/_cmp_ptr(bytewise unsigned, prefix
first, sign only);proven_u8str_view_is_well_formed. Teststest_unit_u8str_view_cmp,
test_unit_u8str_view_ops,test_unit_u8str_split,test_regression_split_empty_sep,
test_differential_find_last_oracle(60,000 cases; planted defects caught). Manual chapter 3
section 1 in both editions withex_03_view_ops. The RFC-0004 benchmark now measures the
shipped iterator: 18.1 ns/field against 16.5 for a correct hand-rolled loop (median of three).
Changed
-
On a Windows console, sysio writes and reads UTF-16.
proven_print/proven_eprint, the
stdout/stderr writers, the stdin reader,proven_sysio_*_buffered, the u8 and u16 line
readers andproven_sysio_scanner_tdetect a console once (GetConsoleMode) and use
WriteConsoleW/ReadConsoleW, converting at the edge. Before, UTF-8 was handed to the
console withWriteFileand shown in the console's code page - mojibake under 949 unless
chcp 65001had been run - and console input came back in that code page. The console's code
page is not changed. Malformed UTF-8 sent to a console is refused after the valid part; a
character split across buffered flushes is carried in the state struct; Ctrl+Z at the start of
a console line is end of input. Files, pipes and redirected streams stay byte-exact; POSIX is
unchanged.proven_writer_from_fileon a console handle stays byte-exact, as documented. -
The allocator pairing of owned strings is now stated as a warning (B-023, owner decision
2026-09-28):u8str.h/u16str.hand manual chapter 3 say that the string does not remember its
allocator and nothing checks it, with a counter-example. No field was added; an allocator-side
ownership check is proposed as B-040. -
proven_sysio_std_tgainsconsoleandcarry(proven_sysio_carry_t);
proven_sysio_scanner_tgains the same two fields. Layout change for code that declares them. -
The manual PDFs are reproducible.
scripts/build-site.shsetsSOURCE_DATE_EPOCHfrom the
commit being built (a caller's own value wins), so the same commit gives byte-identical PDFs -
two full builds matched by SHA-256 for both editions; before, they matched only in size.
scripts/release.shnow compares an existing release asset with the built one by SHA-256
(GitHub's asset digest, or the downloaded asset), not by size.
Fixed
- Code review of the unreleased work (2026-09-28), ten findings:
- A buffered writer's automatic drain flushed the inner writer, and on a Windows console that
flush ended the text: valid UTF-8 split at a buffer boundary came back INVALID_ENCODING with
bytes lost. Drains no longer flush the inner writer, and the console writer's flush keeps an
open character for the next write. Reproduced on the Win11 VM before (FAIL) and fixed after
(win64/win32 18/18). - A non-NULL empty view passed through
remove_prefix,remove_suffixandsplitas{p, 0};
every empty result is now{NULL, 0}as documented. - The Windows symlink kind is decided from a path normalised before any
\\?\prefix.
Defensive: the long-path case the review predicted did not fail on Windows 11 before the fix. proven_u16_reader_tstages 1 KiB with a cursor: about one source read per KiB instead of
one per 64 bytes.utf.h's append functions grow once and convert in place (no chunk copy, no rollback).- One overlap rule (
proven_range_overlaps) for u16 input inutf.hand the formatter; the
formatter used to accept a view running into its output from below. - One padding rule (
spec_padding) for plain, custom and UTF-16 fields. build-b033-check.shprints its report and cleans up when the check fails;release.sh
never deletes an asset because a digest download failed.- New comments are ASCII.
- A buffered writer's automatic drain flushed the inner writer, and on a Windows console that
- Windows symlinks (B-033).
proven_fs_symlinkcreated a link to a directory as a file
link, which cannot be listed or entered, and made every relative target absolute against the
current directory (sub/rel -> tpointed at./t), because the target went through the
helper that callsGetFullPathNameW. The target is now stored as written (with/as\),
the directory flag follows the target as the link resolves it, older Windows without
ALLOW_UNPRIVILEGED_CREATEis retried, and failures arePROVEN_ERR_PERMISSIONor
PROVEN_ERR_NOT_FOUNDwhere they can be told apart (POSIX too), not alwaysPROVEN_ERR_IO.
Measured on the Win11 VM before (7 of 12 failed) and after (win64 12/12, win32 11/11) with
b033-windows-check.c, which also filled a 4 GiB + 4 KiB entropy request across the
32-bit count boundary. proven_time_u16_fmtwidened each UTF-8 byte into a code unit. A caller-supplied locale
with non-ASCII names produced three meaningless units per Hangul syllable; it now transcodes.
Reproduced red first intests/test_unit_time_fmt_u16_parity.
Verification
- New tests:
test_unit_utf(every scalar value; UTF-8 validity against an independent
formulation over every 1-3 byte input; planted defects caught),test_unit_stream_u16,
test_unit_sysio_console(a fake console at every split offset and read size). Debug build:
209 executables. Windows 11 VM, 2026-09-27:b039-check-win64.exeand-win32.exe17/17
each - console output under code page 949, injected console input, pipes and files../nob crosson arch-dev: all 11 targets. Not measured: console input typed through an IME by a
person; a legacy (pre-Windows 10) console host.