AxlFormat — Printf Engine

AxlFormat — Printf Engine

Callback-driven printf engine. Format text directly into any sink (buffer, network socket, file, hash) without intermediate allocation.

This is the engine behind axl_printf, axl_snprintf, axl_asprintf, and axl_string_append_printf. It has zero dependencies (no memory allocator, no I/O) — it breaks the Log -> Data circular dependency by being self-contained.

Header: <axl/axl-format.h>

Callback-Driven Formatting

The core API takes a write callback that receives formatted output in chunks. No memory is allocated — all formatting uses a small stack buffer.

#include <axl.h>

// Write directly to a TCP socket
void net_write(const char *data, size_t len, void *ctx) {
    axl_tcp_send((AxlTcp *)ctx, data, len, 0);
}

// Format an HTTP request line directly into the socket
axl_format(net_write, sock, "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n",
           path, host);

API

Two functions:

  • axl_format(write_fn, ctx, fmt, ...) — variadic convenience

  • axl_vformat(write_fn, ctx, fmt, args) — va_list version

The callback type:

typedef void (*AxlWriteFunc)(const char *data, size_t len, void *ctx);

Use Cases

  • Format directly into a network send buffer (no intermediate string)

  • Stream formatted output into a hash computation

  • Write a custom logging backend that formats in-place

  • Build protocol messages without allocating temporary strings

Supported Format Specifiers

Specifier

Type

Example

%s

char * string

axl_printf("%s", name)

%d

signed int

axl_printf("%d", -42)

%u

unsigned int

axl_printf("%u", 42)

%x / %X

hex (lower/upper)

axl_printf("0x%x", 0xFF)

%llu

uint64_t

axl_printf("%llu", big)

%zu

size_t

axl_printf("%zu", len)

%c

char

axl_printf("%c", ch)

%p

pointer

axl_printf("%p", ptr)

%%

literal %

axl_printf("100%%")

Width and zero-padding are supported: %08x, %-20s, %5d.

API Reference

Defines

AXL_DTOA_BUF_MIN

Minimum bufsz for axl_dtoa: the shortest decimal representation of any IEEE-754 double needs at most 17 significant digits; +1 for the NUL terminator axl_dtoa writes.

Typedefs

typedef void (*AxlWriteFunc)(const char *data, size_t len, void *ctx)

axl-format.h:

Callback-driven printf engine. Format text directly into any sink (buffer, network socket, file, hash) without intermediate allocation.

This is the engine behind axl_printf, axl_snprintf, axl_asprintf, and axl_string_append_printf. Exposed for consumers who need custom formatting targets.

Supports: d i u x X s c p %% Length modifiers: l ll z Flags: 0 - + (space) Width: N or * Precision: .N or .* AxlWriteFunc:

Callback invoked by the format engine to emit output. May be called multiple times per format call (once per literal segment and once per formatted argument).

Functions

void axl_vformat(AxlWriteFunc write_fn, void *ctx, const char *fmt, va_list args)

Format with va_list into a write callback.

The engine calls write_fn one or more times with formatted output segments. No memory is allocated — all formatting uses a small stack buffer.

void my_write(const char *data, size_t len, void *ctx) {
    axl_tcp_send((AxlTcp *)ctx, data, len, 0);
}

va_list args;
va_start(args, fmt);
axl_vformat(my_write, sock, fmt, args);
va_end(args);
Parameters:
  • write_fn – output callback

  • ctx – passed to write_fn

  • fmt – printf-style format string

  • args – format arguments

void axl_format(AxlWriteFunc write_fn, void *ctx, const char *fmt, ...)

Format into a write callback (variadic wrapper).

Convenience wrapper around axl_vformat.

void buf_write(const char *data, size_t len, void *ctx) {
    // append to a custom buffer
}
axl_format(buf_write, &my_buf, "count=%d name=%s", 42, "AXL");
Parameters:
  • write_fn – output callback

  • ctx – passed to write_fn

  • fmt – printf-style format string

Param :

format arguments

int axl_dtoa(double value, char *buf, size_t bufsz, int *out_decpt, int *out_neg)

Shortest round-trippable decimal digits of a double (Grisu2).

Converts the finite double value to the shortest string of decimal digits that, when read back, reproduces value exactly (round-trip). This is the engine behind f / e / g and the primitive a consumer needs to serialize a double without losing precision.

Output is split into three pieces so the caller can render any C float format from one conversion:

  • buf receives the significant digits as ASCII ‘0’..’9’, with no sign, no decimal point, and no exponent. NUL-terminated.

  • out_decpt receives the position of the decimal point measured in digits from the start of buf: the value’s magnitude is 0.<digits> x 10^(*out_decpt) … equivalently <digits-as-integer> x 10^(*out_decpt - ndigits). So *out_decpt is the count of digits that belong to the left of the decimal point (it may be <= 0 or > ndigits). Examples: 1.5 -> “15”, decpt 1; 0.001 -> “1”, decpt -2; 100.0 -> “1”, decpt 3.

  • out_neg receives true for a negative value (including -0.0), false otherwise.

Zero yields “0” with *out_decpt == 1. The result is the canonical shortest form: trailing zeros are not emitted (100.0 is “1” with decpt 3, not “100”).

value MUST be finite. NaN and +/-infinity are NOT handled here (callers detect them first — v != v for NaN, |v| > DBL_MAX for infinity); passing one returns 0.

No allocation, no libm, no libc. Uses a ~1.3KB cached-powers table.

Parameters:
  • value – finite value to convert

  • buf – [out] digit buffer (>= AXL_DTOA_BUF_MIN bytes)

  • bufsz – size of buf

  • out_decpt – [out] decimal-point position (NULL OK)

  • out_neg – [out] 1 if negative (NULL OK)

Returns:

number of digits written to buf (>= 1), or 0 on error (buf NULL, bufsz < AXL_DTOA_BUF_MIN, or value non-finite). out_decpt / out_neg may be NULL to skip.