commit 37ad8f0e64ce819ad11ede23f6f2ff7e97a696fe
parent d364b563010b67b4db8b36087a75ab7a850db57a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 20 May 2026 08:21:56 -0700
Enable runtime printf float formatting
Diffstat:
10 files changed, 476 insertions(+), 38 deletions(-)
diff --git a/rt/include/stdio.h b/rt/include/stdio.h
@@ -11,5 +11,7 @@ int snprintf(char* s, size_t n, const char* fmt, ...);
int vsnprintf(char* s, size_t n, const char* fmt, va_list ap);
int sprintf(char* s, const char* fmt, ...);
int vsprintf(char* s, const char* fmt, va_list ap);
+int fctprintf(void (*out)(char ch, void* arg), void* arg,
+ const char* fmt, ...);
#endif
diff --git a/rt/lib/stdio/printf.c b/rt/lib/stdio/printf.c
@@ -5,34 +5,28 @@
// Floating-point formats are intentionally omitted until cfree's backends can
// compile the original mpaland conversion path portably.
//
-// TODO:
-// - Re-enable mpaland float/exponential formats once all cfree backends can
-// lower the formatter's FP-to-integer conversion path.
-// - Re-enable the mpaland output-callback form after the x64 backend reliably
-// handles the original indirect output dispatch and larger helper signatures.
-// Upstream shape:
-// typedef void (*out_fct_type)(char ch, void* buffer,
-// size_t idx, size_t maxlen);
-// typedef struct { void (*fct)(char ch, void* arg); void* arg; }
-// out_fct_wrap_type;
-// int fctprintf(void (*out)(char ch, void* arg), void* arg,
-// const char* format, ...);
-// fctprintf creates an out_fct_wrap_type on its stack and calls the shared
-// formatter with an _out_fct adapter that invokes wrap->fct(ch, wrap->arg).
//===----------------------------------------------------------------------===//
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
+typedef void (*CfreePrintfCallback)(char ch, void* arg);
+
typedef struct {
char* buf;
size_t cap;
size_t len;
+ CfreePrintfCallback fct;
+ void* arg;
} CfreePrintfOut;
static void cfree_out_ch(CfreePrintfOut* out, char ch) {
- if (out->cap && out->len + 1U < out->cap) out->buf[out->len] = ch;
+ if (out->fct) {
+ out->fct(ch, out->arg);
+ } else if (out->cap && out->len + 1U < out->cap) {
+ out->buf[out->len] = ch;
+ }
out->len++;
}
@@ -86,6 +80,228 @@ static void cfree_out_uint(CfreePrintfOut* out, unsigned long long value,
}
}
+static void cfree_out_padded_string(CfreePrintfOut* out, const char* s,
+ unsigned len, int neg, int plus,
+ int space, int left, int zero,
+ unsigned width) {
+ unsigned prefix = (neg || plus || space) ? 1U : 0U;
+ char sign = neg ? '-' : (plus ? '+' : ' ');
+ char pad = (zero && !left) ? '0' : ' ';
+
+ if (!left && pad == ' ' && width > len + prefix) {
+ cfree_out_repeat(out, ' ', width - len - prefix);
+ }
+ if (prefix) cfree_out_ch(out, sign);
+ if (!left && pad == '0' && width > len + prefix) {
+ cfree_out_repeat(out, '0', width - len - prefix);
+ }
+ for (unsigned i = 0; i < len; i++) cfree_out_ch(out, s[i]);
+ if (left && width > len + prefix) {
+ cfree_out_repeat(out, ' ', width - len - prefix);
+ }
+}
+
+static int cfree_double_negative(double v) {
+ union {
+ double d;
+ uint64_t u;
+ } bits;
+ bits.d = v;
+ return (int)(bits.u >> 63);
+}
+
+static double cfree_double_abs(double v) { return v < 0.0 ? -v : v; }
+
+static double cfree_pow10_u(unsigned n) {
+ double value = 1.0;
+ while (n-- > 0) value *= 10.0;
+ return value;
+}
+
+static void cfree_format_fixed_abs(CfreePrintfOut* out, double value,
+ unsigned prec, int alt) {
+ double rounded;
+ double pow10 = 1.0;
+ int emitted = 0;
+
+ if (prec > 18U) prec = 18U;
+ rounded = value + 0.5 / cfree_pow10_u(prec);
+
+ if (rounded >= 1.0) {
+ for (;;) {
+ double next = pow10 * 10.0;
+ if (!(next > pow10) || next > rounded) break;
+ pow10 = next;
+ }
+ }
+
+ while (pow10 >= 1.0) {
+ int digit = (int)(rounded / pow10);
+ if (digit < 0) digit = 0;
+ if (digit > 9) digit = 9;
+ cfree_out_ch(out, (char)('0' + digit));
+ rounded -= (double)digit * pow10;
+ pow10 /= 10.0;
+ emitted = 1;
+ }
+ if (!emitted) cfree_out_ch(out, '0');
+
+ if (prec != 0 || alt) cfree_out_ch(out, '.');
+ for (unsigned i = 0; i < prec; i++) {
+ int digit;
+ rounded *= 10.0;
+ digit = (int)rounded;
+ if (digit < 0) digit = 0;
+ if (digit > 9) digit = 9;
+ cfree_out_ch(out, (char)('0' + digit));
+ rounded -= (double)digit;
+ }
+}
+
+static int cfree_decimal_exp(double value) {
+ int exp = 0;
+ if (value == 0.0) return 0;
+ while (value >= 10.0) {
+ value /= 10.0;
+ exp++;
+ }
+ while (value < 1.0) {
+ value *= 10.0;
+ exp--;
+ }
+ return exp;
+}
+
+static double cfree_normalize_decimal(double value, int* exp) {
+ *exp = 0;
+ if (value == 0.0) return 0.0;
+ while (value >= 10.0) {
+ value /= 10.0;
+ (*exp)++;
+ }
+ while (value < 1.0) {
+ value *= 10.0;
+ (*exp)--;
+ }
+ return value;
+}
+
+static void cfree_format_exp_abs(CfreePrintfOut* out, double value,
+ unsigned prec, int upper, int alt) {
+ int exp;
+ unsigned e;
+ char digits[12];
+ unsigned len = 0;
+ double norm;
+
+ if (prec > 18U) prec = 18U;
+ norm = cfree_normalize_decimal(value, &exp);
+ if (norm + 0.5 / cfree_pow10_u(prec) >= 10.0) {
+ norm /= 10.0;
+ exp++;
+ }
+
+ cfree_format_fixed_abs(out, norm, prec, alt);
+ cfree_out_ch(out, upper ? 'E' : 'e');
+ if (exp < 0) {
+ cfree_out_ch(out, '-');
+ e = (unsigned)-exp;
+ } else {
+ cfree_out_ch(out, '+');
+ e = (unsigned)exp;
+ }
+
+ do {
+ digits[len++] = (char)('0' + (e % 10U));
+ e /= 10U;
+ } while (e != 0U);
+ while (len < 2U) digits[len++] = '0';
+ while (len > 0) cfree_out_ch(out, digits[--len]);
+}
+
+static void cfree_trim_float(char* s, unsigned* len, int alt) {
+ unsigned end = *len;
+ unsigned exp_start = end;
+ unsigned trim_end;
+ unsigned dot_pos = end;
+ unsigned i;
+
+ if (alt) return;
+ for (i = 0; i < end; i++) {
+ if (s[i] == 'e' || s[i] == 'E') {
+ exp_start = i;
+ break;
+ }
+ }
+ trim_end = exp_start;
+ for (i = 0; i < exp_start; i++) {
+ if (s[i] == '.') {
+ dot_pos = i;
+ break;
+ }
+ }
+ if (dot_pos == end) return;
+
+ while (trim_end > dot_pos + 1U && s[trim_end - 1U] == '0') trim_end--;
+ if (trim_end > dot_pos && s[trim_end - 1U] == '.') trim_end--;
+
+ if (exp_start < end) {
+ unsigned src = exp_start;
+ while (src < end) s[trim_end++] = s[src++];
+ }
+ *len = trim_end;
+ s[*len] = '\0';
+}
+
+static void cfree_format_float(CfreePrintfOut* out, double value, char spec,
+ int left, int zero, int plus, int space,
+ int alt, unsigned width, int has_prec,
+ unsigned prec) {
+ char tmp[384];
+ CfreePrintfOut tmp_out;
+ unsigned len;
+ int neg = cfree_double_negative(value);
+ double mag = cfree_double_abs(value);
+
+ if (!has_prec) prec = 6U;
+ tmp_out.buf = tmp;
+ tmp_out.cap = sizeof(tmp);
+ tmp_out.len = 0U;
+ tmp_out.fct = NULL;
+ tmp_out.arg = NULL;
+
+ if (value != value) {
+ cfree_out_string(&tmp_out, (spec >= 'A' && spec <= 'Z') ? "NAN" : "nan",
+ 0, 0, 0, 0);
+ neg = 0;
+ } else if (mag > 1.7976931348623157e308) {
+ cfree_out_string(&tmp_out, (spec >= 'A' && spec <= 'Z') ? "INF" : "inf",
+ 0, 0, 0, 0);
+ } else if (spec == 'e' || spec == 'E') {
+ cfree_format_exp_abs(&tmp_out, mag, prec, spec == 'E', alt);
+ } else if (spec == 'g' || spec == 'G') {
+ int exp = cfree_decimal_exp(mag);
+ if (prec == 0U) prec = 1U;
+ if (exp < -4 || exp >= (int)prec) {
+ cfree_format_exp_abs(&tmp_out, mag, prec - 1U, spec == 'G', alt);
+ } else {
+ unsigned fprec = exp >= 0 ? prec - (unsigned)exp - 1U
+ : prec + (unsigned)(-exp) - 1U;
+ cfree_format_fixed_abs(&tmp_out, mag, fprec, alt);
+ }
+ } else {
+ cfree_format_fixed_abs(&tmp_out, mag, prec, alt);
+ }
+
+ len = tmp_out.len < sizeof(tmp) ? (unsigned)tmp_out.len
+ : (unsigned)sizeof(tmp) - 1U;
+ tmp[len] = '\0';
+ if (spec == 'g' || spec == 'G') cfree_trim_float(tmp, &len, alt);
+ if (value != value) neg = 0;
+ cfree_out_padded_string(out, tmp, len, neg, plus && !neg, space && !neg,
+ left, zero, width);
+}
+
static int cfree_is_digit(char ch) { return ch >= '0' && ch <= '9'; }
static unsigned cfree_parse_uint(const char** p) {
@@ -96,16 +312,15 @@ static unsigned cfree_parse_uint(const char** p) {
return value;
}
-static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt,
- va_list ap) {
- CfreePrintfOut out;
- out.buf = s;
- out.cap = s ? n : 0U;
- out.len = 0U;
+static int cfree_vprintf_impl(CfreePrintfOut* out, const char* fmt,
+ va_list ap) {
while (*fmt) {
int left = 0;
int zero = 0;
+ int plus = 0;
+ int space = 0;
+ int alt = 0;
int long_mod = 0;
int long_long_mod = 0;
int has_prec = 0;
@@ -113,12 +328,12 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt,
unsigned prec = 0;
if (*fmt != '%') {
- cfree_out_ch(&out, *fmt++);
+ cfree_out_ch(out, *fmt++);
continue;
}
fmt++;
if (*fmt == '%') {
- cfree_out_ch(&out, *fmt++);
+ cfree_out_ch(out, *fmt++);
continue;
}
@@ -129,6 +344,15 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt,
} else if (*fmt == '0') {
zero = 1;
fmt++;
+ } else if (*fmt == '+') {
+ plus = 1;
+ fmt++;
+ } else if (*fmt == ' ') {
+ space = 1;
+ fmt++;
+ } else if (*fmt == '#') {
+ alt = 1;
+ fmt++;
} else {
break;
}
@@ -165,7 +389,7 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt,
v = va_arg(ap, int);
}
mag = v < 0 ? 0ULL - (unsigned long long)v : (unsigned long long)v;
- cfree_out_uint(&out, mag, 10U, 0, v < 0, left, zero, width);
+ cfree_out_uint(out, mag, 10U, 0, v < 0, left, zero, width);
break;
}
case 'u':
@@ -181,34 +405,57 @@ static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt,
} else {
v = va_arg(ap, unsigned int);
}
- cfree_out_uint(&out, v, base, *fmt == 'X', 0, left, zero, width);
+ cfree_out_uint(out, v, base, *fmt == 'X', 0, left, zero, width);
break;
}
case 'p': {
uintptr_t v = (uintptr_t)va_arg(ap, void*);
- cfree_out_string(&out, "0x", 0, 0, 0, 0);
- cfree_out_uint(&out, (unsigned long long)v, 16U, 0, 0, 0, 0, 0);
+ cfree_out_string(out, "0x", 0, 0, 0, 0);
+ cfree_out_uint(out, (unsigned long long)v, 16U, 0, 0, 0, 0, 0);
break;
}
case 'c':
- cfree_out_ch(&out, (char)va_arg(ap, int));
+ cfree_out_ch(out, (char)va_arg(ap, int));
break;
case 's':
- cfree_out_string(&out, va_arg(ap, const char*), left, width, has_prec,
+ cfree_out_string(out, va_arg(ap, const char*), left, width, has_prec,
prec);
break;
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ cfree_format_float(out, va_arg(ap, double), *fmt, left, zero, plus,
+ space, alt, width, has_prec, prec);
+ break;
default:
- if (*fmt) cfree_out_ch(&out, *fmt);
+ if (*fmt) cfree_out_ch(out, *fmt);
break;
}
if (*fmt) fmt++;
}
+ return (int)out->len;
+}
+
+static int cfree_vsnprintf_impl(char* s, size_t n, const char* fmt,
+ va_list ap) {
+ CfreePrintfOut out;
+ int rc;
+ out.buf = s;
+ out.cap = s ? n : 0U;
+ out.len = 0U;
+ out.fct = NULL;
+ out.arg = NULL;
+
+ rc = cfree_vprintf_impl(&out, fmt, ap);
if (out.cap) {
size_t pos = out.len < out.cap ? out.len : out.cap - 1U;
out.buf[pos] = '\0';
}
- return (int)out.len;
+ return rc;
}
int vsnprintf(char* s, size_t n, const char* fmt, va_list ap) {
@@ -241,3 +488,19 @@ int sprintf(char* s, const char* fmt, ...) {
va_end(ap);
return rc;
}
+
+int fctprintf(void (*out_fct)(char ch, void* arg), void* arg,
+ const char* fmt, ...) {
+ CfreePrintfOut out;
+ va_list ap;
+ int rc;
+ out.buf = NULL;
+ out.cap = 0U;
+ out.len = 0U;
+ out.fct = out_fct;
+ out.arg = arg;
+ va_start(ap, fmt);
+ rc = cfree_vprintf_impl(&out, fmt, ap);
+ va_end(ap);
+ return rc;
+}
diff --git a/src/cg/call.c b/src/cg/call.c
@@ -77,6 +77,42 @@ void api_push_call_result(CfreeCg* g, Operand ret_storage,
}
}
+static void api_spill_call_clobbered_stack(CfreeCg* g, const CGCallDesc* d) {
+ CGTarget* T = g->target;
+ u32 masks[3];
+ if (cg_simple_regalloc_is_virtual(&g->regalloc)) return;
+ if (!T->call_clobber_mask) return;
+ for (u32 c = 0; c < 3u; ++c) {
+ masks[c] = T->call_clobber_mask(T, d, (RegClass)c);
+ }
+ for (u32 i = 0; i < g->sp; ++i) {
+ ApiSValue* sv = &g->stack[i];
+ u8 cls;
+ Reg reg;
+ FrameSlot slot;
+ Operand src;
+ if (sv->res != RES_REG) continue;
+ cls = api_class_of_sv(sv);
+ if (cls >= 3u) continue;
+ reg = api_reg_of_sv(sv);
+ if (reg == (Reg)REG_NONE || reg >= 32u) continue;
+ if ((masks[cls] & (1u << reg)) == 0) continue;
+ if (sv->pinned) {
+ compiler_panic(g->c, g->cur_loc,
+ "CfreeCg: call clobbers pinned register %u class %u",
+ (unsigned)reg, (unsigned)cls);
+ continue;
+ }
+ slot = api_take_spill_slot(g, cls);
+ src = api_op_reg(reg, api_owned_reg_type(g, sv));
+ T->spill_reg(T, src, slot, api_mem_for_spill(g, sv));
+ api_free_reg(g, reg, cls);
+ api_set_owned_reg(sv, (Reg)REG_NONE);
+ sv->spill_slot = slot;
+ sv->res = RES_SPILLED;
+ }
+}
+
void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type,
CfreeCgCallAttrs attrs) {
CGTarget* T;
@@ -133,6 +169,7 @@ void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type,
}
if (tail) api_regalloc_finish(g);
+ if (!tail) api_spill_call_clobbered_stack(g, &desc);
T->call(T, &desc);
api_release_call_args(g, avs, nargs);
@@ -248,6 +285,7 @@ void api_call_symbol_common(CfreeCg* g, CfreeCgSym sym, uint32_t nargs,
desc.ret.storage = api_op_imm(0, builtin_id(CFREE_CG_BUILTIN_VOID));
}
if (tail) api_regalloc_finish(g);
+ if (!tail) api_spill_call_clobbered_stack(g, &desc);
T->call(T, &desc);
api_release_call_args(g, avs, nargs);
if (has_result) {
diff --git a/src/cg/memory.c b/src/cg/memory.c
@@ -530,6 +530,23 @@ void cfree_cg_dup(CfreeCg* g) {
api_ensure_reg(g, top);
v = *top;
if (v.res != RES_REG) {
+ if (v.res == RES_FIXED_REG && !api_is_lvalue_sv(&v) &&
+ v.op.kind == OPK_REG) {
+ ty = api_owned_reg_type(g, &v);
+ r = api_alloc_reg_or_spill(g, api_class_of_sv(&v), ty);
+ dst = api_op_reg(r, ty);
+ g->target->copy(g->target, dst,
+ api_op_reg((Reg)api_reg_of_sv(&v), ty));
+ dup = v;
+ api_set_owned_reg(&dup, r);
+ dup.res = RES_REG;
+ dup.pinned = 0;
+ dup.spill_slot = FRAME_SLOT_NONE;
+ dup.source_local = CFREE_CG_LOCAL_NONE;
+ g->stack[g->sp - 1] = dup;
+ api_push(g, v);
+ return;
+ }
api_push(g, v);
return;
}
diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md
@@ -169,6 +169,8 @@ here for completeness once they're real cases.
| `6_5_31_subscript_commute`| ★ | `int a[5]={0,0,42,0,0}; return 2[a];` | 42 |
| `6_5_32_string_subscript` | ★ | `return "*"[0];` | 42 |
| `6_5_33_regalloc_spill` | ★ | 12-arg `sum12(x1+0, ..., x12+0)` — exceeds the 10-INT scratch pool, exercises `spill_reg`/`reload_reg` and the cg_call avs-in-flight fallback | 78 |
+| `cg_postdec_while_count` | ★ | `while (n-- > 0)` must test the old value after duplicating a register-backed local | 42 |
+| `cg_x64_fp_format_fixed_digits` | ★ | fixed decimal digit extraction from `12.375` with a helper call and output calls; repro for x64 caller-saved FP values and post-decrement rounding | 42 |
| `6_5_36_fp_arith` | ★ | `(a + b) / b * c - 36.0` over `double` — pins parser dispatch to `BO_FADD`/`FSUB`/`FMUL`/`FDIV` | 42 |
| `6_5_37_fp_int_promote` | ★ | `int + double` — usual arithmetic conversion promotes the int side to `double` before BO_FADD | 42 |
| `6_5_38_fp_float_widen` | ★ | `float + double` — float widens to double before BO_FADD | 42 |
diff --git a/test/parse/cases/cg_postdec_while_count.c b/test/parse/cases/cg_postdec_while_count.c
@@ -0,0 +1,11 @@
+static unsigned pow10_count(unsigned n) {
+ unsigned value = 1;
+ while (n-- > 0) value *= 10;
+ return value;
+}
+
+int test_main(void) {
+ unsigned got = pow10_count(2);
+ if (got != 100U) return (int)got;
+ return 42;
+}
diff --git a/test/parse/cases/cg_postdec_while_count.expected b/test/parse/cases/cg_postdec_while_count.expected
@@ -0,0 +1 @@
+42
diff --git a/test/parse/cases/cg_x64_fp_format_fixed_digits.c b/test/parse/cases/cg_x64_fp_format_fixed_digits.c
@@ -0,0 +1,72 @@
+typedef unsigned long size_t;
+
+typedef struct {
+ char* buf;
+ size_t cap;
+ size_t len;
+} Out;
+
+static void out_ch(Out* out, char ch) {
+ if (out->cap && out->len + 1U < out->cap) out->buf[out->len] = ch;
+ out->len++;
+}
+
+static double pow10_u(unsigned n) {
+ double value = 1.0;
+ while (n-- > 0) value *= 10.0;
+ return value;
+}
+
+static void format_fixed_abs(Out* out, double value, unsigned prec) {
+ double rounded;
+ double pow10 = 1.0;
+ int emitted = 0;
+
+ rounded = value + 0.5 / pow10_u(prec);
+ if (rounded >= 1.0) {
+ for (;;) {
+ double next = pow10 * 10.0;
+ if (!(next > pow10) || next > rounded) break;
+ pow10 = next;
+ }
+ }
+
+ while (pow10 >= 1.0) {
+ int digit = (int)(rounded / pow10);
+ if (digit < 0) digit = 0;
+ if (digit > 9) digit = 9;
+ out_ch(out, (char)('0' + digit));
+ rounded -= (double)digit * pow10;
+ pow10 /= 10.0;
+ emitted = 1;
+ }
+ if (!emitted) out_ch(out, '0');
+
+ out_ch(out, '.');
+ for (unsigned i = 0; i < prec; i++) {
+ int digit;
+ rounded *= 10.0;
+ digit = (int)rounded;
+ if (digit < 0) digit = 0;
+ if (digit > 9) digit = 9;
+ out_ch(out, (char)('0' + digit));
+ rounded -= (double)digit;
+ }
+}
+
+int test_main(void) {
+ char buf[16];
+ Out out;
+ out.buf = buf;
+ out.cap = sizeof(buf);
+ out.len = 0;
+ format_fixed_abs(&out, 12.375, 2);
+ buf[out.len] = '\0';
+ if (buf[0] != '1') return 20;
+ if (buf[1] != '2') return 21;
+ if (buf[2] != '.') return 22;
+ if (buf[3] != '3') return 23;
+ if (buf[4] != '8') return 24;
+ if (out.len != 5) return 25;
+ return 42;
+}
diff --git a/test/parse/cases/cg_x64_fp_format_fixed_digits.expected b/test/parse/cases/cg_x64_fp_format_fixed_digits.expected
@@ -0,0 +1 @@
+42
diff --git a/test/rt/cases/freestanding_lib.c b/test/rt/cases/freestanding_lib.c
@@ -29,6 +29,19 @@ static int check_vsprintf(char* out, const char* fmt, ...) {
return rc;
}
+struct CallbackOut {
+ char buf[32];
+ int len;
+};
+
+static void capture_ch(char ch, void* arg) {
+ struct CallbackOut* out = (struct CallbackOut*)arg;
+ if (out->len < (int)sizeof(out->buf) - 1) {
+ out->buf[out->len] = ch;
+ }
+ out->len++;
+}
+
static int strings_ok(void) {
char buf[32];
char small[8];
@@ -99,26 +112,44 @@ static int stdio_ok(void) {
int rc;
rc = snprintf(buf, sizeof(buf), "%s:%d:%04x:%c", "id", -7, 26, '!');
- if (rc != 12 || strcmp(buf, "id:-7:001a:!") != 0) return 0;
+ if (rc != 12 || strcmp(buf, "id:-7:001a:!") != 0) return 31;
rc = snprintf(buf, 6, "abcdef");
- if (rc != 6 || strcmp(buf, "abcde") != 0) return 0;
+ if (rc != 6 || strcmp(buf, "abcde") != 0) return 32;
rc = sprintf(buf, "%u/%ld/%p", 12U, 34L, (void*)0x1234);
- if (rc <= 0 || strstr(buf, "12/34/") != buf) return 0;
+ if (rc <= 0 || strstr(buf, "12/34/") != buf) return 33;
rc = check_vsnprintf(buf, sizeof(buf), "%s %02d", "v", 3);
- if (rc != 4 || strcmp(buf, "v 03") != 0) return 0;
+ if (rc != 4 || strcmp(buf, "v 03") != 0) return 34;
rc = check_vsprintf(buf, "%lld", 123456789LL);
- if (rc != 9 || strcmp(buf, "123456789") != 0) return 0;
- return 1;
+ if (rc != 9 || strcmp(buf, "123456789") != 0) return 35;
+
+ rc = snprintf(buf, sizeof(buf), "%.2f/%+.1f", 12.375, 4.26);
+ if (rc != 10 || strcmp(buf, "12.38/+4.3") != 0) return 36;
+
+ rc = snprintf(buf, sizeof(buf), "%.2e %.4g", 1234.0, 12.340);
+ if (rc != 14 || strcmp(buf, "1.23e+03 12.34") != 0) return 37;
+
+ {
+ struct CallbackOut out;
+ out.len = 0;
+ memset(out.buf, 0, sizeof(out.buf));
+ rc = fctprintf(capture_ch, &out, "%s:%04d", "cb", 9);
+ if (out.len >= (int)sizeof(out.buf)) return 38;
+ out.buf[out.len] = '\0';
+ if (rc != 7 || strcmp(out.buf, "cb:0009") != 0) return 39;
+ }
+ return 0;
}
int test_main(void) {
+ int rc;
assert(1);
if (!strings_ok()) return 1;
if (!stdlib_ok()) return 2;
- if (!stdio_ok()) return 3;
+ rc = stdio_ok();
+ if (rc != 0) return rc;
return 42;
}