fix(runtime): mirror surface .0-fallback in ailang_float_to_str
`runtime/str.c::ailang_float_to_str` now applies the `.0`-fallback
already enforced by the surface printer's `write_float_lit`
(crates/ailang-surface/src/print.rs lines 624-637): after the
existing `%g` snprintf, if `x` is finite and the rendered buffer
contains neither `.` nor `e`/`E`, append `.0` so a whole-valued
Float renders with Float-shaped text. The `isfinite(x)` fence
keeps `nan`/`inf`/`-inf` from matching the predicate and turning
into `nan.0`/`inf.0`. The 64-byte stack buffer's truncation guard
is extended by 2 bytes for the fallback path so the defensive
`abort()` discipline holds.
The alternative (document `%g`-without-fallback in the design/
ledger as a deliberate human-friendly contract) was rejected: the
language's stated ethos is machine-readability and round-trip
identity; the surface printer is the reference, runtime drift
from it is a defect not a feature.
Golden updates flow from the contract change. Three fixtures
encoded the old Int-shaped output for Float values:
- `crates/ail/tests/floats_e2e.rs`: `"4\n42\n-1.5\n"` →
`"4.0\n42.0\n-1.5\n"` (1.5+2.5=4.0, int_to_float(42)=42.0).
- `crates/ail/tests/e2e.rs::mut_sum_floats_prints_55`: `"55"` →
`"55.0"`. The accompanying doc comment used to canonicalise the
bug ("`%g` strips the trailing `.0` — so the canonical stdout
for Float 55.0 is `55`"); rewritten to reflect the new contract.
- `examples/mut_sum_floats.ail` docstring: same correction.
Two doc comments are updated where the post-fix contract differs
from the old text but the fixture golden does not change:
- `examples/float_to_str_smoke.ail` docstring (3.5 still renders
as `3.5` because `.` is already present — the fallback does not
fire).
- `crates/ail/tests/e2e.rs::float_to_str_smoke` doc comment (same
observation, made explicit so the trip-wire's intent is clear).
Verified: `cargo test -p ail --test print_float_whole_e2e` green
(was RED at f19b0dd); `cargo test --workspace` 0 failed across
all crates and integration suites.
closes #7
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Defined in runtime/rc.c — exposed with default external linkage.
|
||||
* Returns a zero-initialised payload whose `rc_header` sits at
|
||||
@@ -125,6 +126,16 @@ char *ailang_int_to_str(int64_t n) {
|
||||
* oversized. Defensive `abort()` on truncation, same shape as
|
||||
* `ailang_int_to_str`.
|
||||
*
|
||||
* `.0`-fallback (Gitea #7): if `x` is finite and `%g`'s output
|
||||
* contains neither `.` nor `e`/`E`, append `.0` so a whole-valued
|
||||
* Float renders with a Float-shaped suffix instead of Int-shaped
|
||||
* text. Mirrors the surface printer's `write_float_lit` fallback
|
||||
* at `crates/ailang-surface/src/print.rs` lines 624-637, keeping
|
||||
* the surface/runtime round-trip-on-lex symmetry intact. The
|
||||
* `isfinite(x)` fence skips the fallback for `nan`/`inf`/`-inf`,
|
||||
* which would otherwise match the no-`.`-no-`e` predicate and
|
||||
* render as `nan.0`/`inf.0`.
|
||||
*
|
||||
* Returns the payload pointer of a fresh heap-Str slab.
|
||||
*/
|
||||
char *ailang_float_to_str(double x) {
|
||||
@@ -138,6 +149,24 @@ char *ailang_float_to_str(double x) {
|
||||
abort();
|
||||
}
|
||||
uint64_t len = (uint64_t)written;
|
||||
/* `.0`-fallback — see block comment above and the surface
|
||||
* counterpart at crates/ailang-surface/src/print.rs lines
|
||||
* 624-637. `%g` output is ASCII, so plain `memchr` suffices. */
|
||||
if (isfinite(x)
|
||||
&& memchr(buf, '.', len) == NULL
|
||||
&& memchr(buf, 'e', len) == NULL
|
||||
&& memchr(buf, 'E', len) == NULL) {
|
||||
if (len + 2 >= sizeof(buf)) {
|
||||
fprintf(stderr,
|
||||
"ailang_float_to_str: .0-fallback would overflow buffer "
|
||||
"(len=%llu, buffer=%zu) — should be unreachable for any finite double; aborting\n",
|
||||
(unsigned long long)len, sizeof(buf));
|
||||
abort();
|
||||
}
|
||||
buf[len] = '.';
|
||||
buf[len + 1] = '0';
|
||||
len += 2;
|
||||
}
|
||||
char *payload = str_alloc(len);
|
||||
memcpy(payload + 8, buf, len);
|
||||
payload[8 + len] = '\0';
|
||||
|
||||
Reference in New Issue
Block a user