Skip to content

File syn_fmt.c

File List > src > syntropic > util > syn_fmt.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_FMT) || SYN_USE_FMT

#include "syn_fmt.h"

/* ── Internal helpers ───────────────────────────────────────────────────── */

static size_t write_char(char *buf, size_t size, size_t pos, char c)
{
    if (pos < size - 1) {
        buf[pos] = c;
    }
    return pos + 1;
}

static size_t write_str(char *buf, size_t size, size_t pos, const char *s)
{
    while (*s) {
        pos = write_char(buf, size, pos, *s++);
    }
    return pos;
}

/* ── Unsigned integer to decimal ────────────────────────────────────────── */

size_t syn_fmt_uint(char *buf, size_t size, uint32_t val)
{
    if (size == 0)
        return 0;

    char tmp[11]; /* max 10 digits for uint32 */
    int i = 0;

    if (val == 0) {
        tmp[i++] = '0';
    } else {
        while (val > 0) {
            tmp[i++] = (char)('0' + (val % 10));
            val /= 10;
        }
    }

    /* Reverse into output */
    size_t pos = 0;
    while (i > 0) {
        pos = write_char(buf, size, pos, tmp[--i]);
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

/* ── Signed integer to decimal ──────────────────────────────────────────── */

size_t syn_fmt_int(char *buf, size_t size, int32_t val)
{
    if (size == 0)
        return 0;

    size_t pos = 0;
    uint32_t uval;

    if (val < 0) {
        pos = write_char(buf, size, pos, '-');
        /* Handle INT32_MIN safely */
        uval = (uint32_t)(-(val + 1)) + 1;
    } else {
        uval = (uint32_t)val;
    }

    char tmp[11];
    int i = 0;

    if (uval == 0) {
        tmp[i++] = '0';
    } else {
        while (uval > 0) {
            tmp[i++] = (char)('0' + (uval % 10));
            uval /= 10;
        }
    }

    while (i > 0) {
        pos = write_char(buf, size, pos, tmp[--i]);
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

/* ── Hex ────────────────────────────────────────────────────────────────── */

static const char hex_chars[] = "0123456789ABCDEF";

size_t syn_fmt_hex(char *buf, size_t size, uint32_t val, uint8_t min_digits)
{
    if (size == 0)
        return 0;
    if (min_digits > 8)
        min_digits = 8;

    char tmp[8];
    int i = 0;

    if (val == 0 && min_digits == 0) {
        min_digits = 1;
    }

    while (val > 0 || i < (int)min_digits) {
        tmp[i++] = hex_chars[val & 0xF];
        val >>= 4;
        if (i >= 8)
            break;
    }

    size_t pos = 0;
    while (i > 0) {
        pos = write_char(buf, size, pos, tmp[--i]);
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

static int hex_char_to_nibble(char c)
{
    if (c >= '0' && c <= '9')
        return (c - '0');
    if (c >= 'a' && c <= 'f')
        return (c - 'a' + 10);
    if (c >= 'A' && c <= 'F')
        return (c - 'A' + 10);
    return -1;
}

size_t syn_fmt_hex_parse(const char *hex_str, uint8_t *out_bin, size_t max_bytes)
{
    if (!hex_str || !out_bin || max_bytes == 0)
        return 0;

    size_t bytes = 0;
    while (hex_str[0] != '\0' && hex_str[1] != '\0' && bytes < max_bytes) {
        int hi = hex_char_to_nibble(hex_str[0]);
        int lo = hex_char_to_nibble(hex_str[1]);
        if (hi < 0 || lo < 0)
            break;
        out_bin[bytes++] = (uint8_t)((hi << 4) | lo);
        hex_str += 2;
    }
    return bytes;
}

/* ── Q16.16 fixed-point ─────────────────────────────────────────────────── */

size_t syn_fmt_q16(char *buf, size_t size, int32_t q16_val, uint8_t frac_digits)
{
    if (size == 0)
        return 0;
    if (frac_digits > 6)
        frac_digits = 6;

    size_t pos = 0;
    uint32_t abs_val;

    if (q16_val < 0) {
        pos = write_char(buf, size, pos, '-');
        abs_val = (uint32_t)(-(q16_val + 1)) + 1;
    } else {
        abs_val = (uint32_t)q16_val;
    }

    /* Integer part */
    uint32_t int_part = abs_val >> 16;
    char tmp[11];
    int i = 0;

    if (int_part == 0) {
        tmp[i++] = '0';
    } else {
        while (int_part > 0) {
            tmp[i++] = (char)('0' + (int_part % 10));
            int_part /= 10;
        }
    }
    while (i > 0) {
        pos = write_char(buf, size, pos, tmp[--i]);
    }

    /* Fractional part */
    if (frac_digits > 0) {
        pos = write_char(buf, size, pos, '.');

        /* frac = (frac_bits / 65536) × 10^frac_digits */
        uint32_t frac_bits = abs_val & 0xFFFF;
        static const uint32_t powers[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000};
        uint8_t idx =
            frac_digits & 0x07u; /* clamp already done above; mask silences OOB analysis */
        uint32_t frac = (uint32_t)(((uint64_t)frac_bits * powers[idx]) >> 16);

        /* Zero-pad to frac_digits width */
        int d;
        for (d = (int)frac_digits - 1; d >= 0; d--) {
            tmp[d] = (char)('0' + (frac % 10));
            frac /= 10;
        }
        for (d = 0; d < (int)frac_digits; d++) {
            pos = write_char(buf, size, pos, tmp[d]);
        }
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

/* ── Hex dump ───────────────────────────────────────────────────────────── */

size_t syn_fmt_hexdump(char *buf, size_t size, const uint8_t *data, size_t len)
{
    if (size == 0)
        return 0;

    size_t pos = 0;
    size_t i;

    for (i = 0; i < len; i++) {
        if (i > 0) {
            pos = write_char(buf, size, pos, ' ');
        }
        pos = write_char(buf, size, pos, hex_chars[(data[i] >> 4) & 0xF]);
        pos = write_char(buf, size, pos, hex_chars[data[i] & 0xF]);
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

/* ── Fixed-point decimal ────────────────────────────────────────────────── */

size_t syn_fmt_fixed(char *buf, size_t size, int32_t val, uint8_t places)
{
    if (size == 0)
        return 0;
    if (places > 9)
        places = 9;

    size_t pos = 0;
    uint32_t abs_val;

    if (val < 0) {
        pos = write_char(buf, size, pos, '-');
        abs_val = (uint32_t)(-(val + 1)) + 1;
    } else {
        abs_val = (uint32_t)val;
    }

    /* Compute divisor = 10^places */
    uint32_t div = 1;
    uint8_t p;
    for (p = 0; p < places; p++)
        div *= 10;

    uint32_t int_part = abs_val / div;
    /* Integer part */
    char tmp[11];
    int i = 0;
    if (int_part == 0) {
        tmp[i++] = '0';
    } else {
        while (int_part > 0) {
            tmp[i++] = (char)('0' + (int_part % 10));
            int_part /= 10;
        }
    }
    while (i > 0) {
        pos = write_char(buf, size, pos, tmp[--i]);
    }

    if (places > 0) {
        pos = write_char(buf, size, pos, '.');

        /* Zero-padded fractional part */
        uint32_t frac_part = abs_val % div;
        int d;
        for (d = (int)places - 1; d >= 0; d--) {
            tmp[d] = (char)('0' + (frac_part % 10));
            frac_part /= 10;
        }
        for (d = 0; d < (int)places; d++) {
            pos = write_char(buf, size, pos, tmp[d]);
        }
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

/* ── Concat ─────────────────────────────────────────────────────────────── */

size_t syn_fmt_concat(char *buf, size_t size, const char *const *parts, size_t n)
{
    if (size == 0)
        return 0;

    size_t pos = 0;
    size_t i;

    for (i = 0; i < n; i++) {
        if (parts[i] != NULL) {
            pos = write_str(buf, size, pos, parts[i]);
        }
    }

    if (pos < size)
        buf[pos] = '\0';
    else
        buf[size - 1] = '\0';

    return (pos < size) ? pos : size - 1;
}

#endif /* SYN_USE_FMT */