MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
numeros_scribo.h
Go to the documentation of this file.
1/* MMgr - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2 * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial OR LicenseRef-Educational
3 *
4 * Every use falls under AGPL-3.0-or-later unless you hold explicit permission, which is either a
5 * negotiated commercial licensing contract or an educator's license issued to you personally.
6 */
7/**
8 * @file numeros_scribo.h
9 * @brief Formatted output: the field kinds, the value union, the macros that build both, and the numer table.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-08-29
12 *
13 * @note A spec is an mmgr_field array ending at MMGR_FK_END. Every field but MMGR_FK_LIT takes one
14 * mmgr_fval.
15 * @note Includes verba_scribo.h, which is where mmgr_config.h and the verba table come from.
16 */
17#ifndef MMGR_NUMEROS_SCRIBO_H
18#define MMGR_NUMEROS_SCRIBO_H
19
21
22EMBED_BEGIN_DECLS
23
24/**
25 * @brief What one field formats, and which mmgr_fval arm it reads.
26 *
27 * @note numeros_scribo.c indexes s_kind by this value, and so MMGR_FK_XML is the largest one it accepts.
28 * @note One byte on every compiler, through EMBED_ENUM_BYTE.
29 */
30EMBED_ENUM_BYTE(mmgr_fk, MMGR_FK_END = 0, /**< Ends a spec. numer_build stops at this field. */
31 MMGR_FK_LIT, /**< Literal text, taken from mmgr_field::lit and mmgr_field::len. */
32 MMGR_FK_STR, /**< String from as.s, through mmgr_verba_put. */
33 MMGR_FK_U32, /**< Unsigned 32-bit from as.u32, base 10, through mmgr_verba_u32. */
34 MMGR_FK_U64, /**< Unsigned 64-bit from as.u64, base 10, through mmgr_verba_u64. */
35 MMGR_FK_I64, /**< Signed 64-bit from as.i64, base 10, through mmgr_verba_i64. */
36 MMGR_FK_DEC, /**< Unsigned 32-bit from as.u32, base 10, through mmgr_verba_u32w. */
37 MMGR_FK_HEX, /**< Unsigned 64-bit from as.u64, base 16, through mmgr_verba_hex. */
38 MMGR_FK_OCT, /**< Unsigned 64-bit from as.u64, base 8, through mmgr_verba_uint. */
39 MMGR_FK_G, /**< Double from as.d, through mmgr_verba_g, defaulting to six significant digits. */
40 MMGR_FK_FIX, /**< Double from as.d, through mmgr_verba_fixed. */
41 MMGR_FK_CH, /**< Single character from as.c, through mmgr_verba_ch. */
42 MMGR_FK_JSON, /**< String from as.s, through mmgr_verba_json. */
43 MMGR_FK_XML /**< String from as.s, through mmgr_verba_xml. */
44);
45
46/**
47 * @brief One field of a spec.
48 *
49 * @note numer_build reads literal and bytes only for MMGR_FK_LIT, and width only for the other kinds.
50 */
51typedef struct
52{
53 mmgr_fk kind; /**< What this field formats. */
54 uint8_t width; /**< Width to give the value, or 0 to take the kind's default from s_kind. */
55 uint16_t bytes; /**< Bytes of literal to write, for MMGR_FK_LIT. */
56 const char *literal; /**< Literal text, for MMGR_FK_LIT [BORROWS]. */
58
59/** @brief Expands to an mmgr_field of kind MMGR_FK_STR, with width 0, bytes 0 and literal NULL. */
60#define MMGR_STR {MMGR_FK_STR, 0u, 0u, NULL}
61
62/** @brief Expands to an mmgr_field of kind MMGR_FK_U32, with width 0, bytes 0 and literal NULL. */
63#define MMGR_U32 {MMGR_FK_U32, 0u, 0u, NULL}
64
65/** @brief Expands to an mmgr_field of kind MMGR_FK_U64, with width 0, bytes 0 and literal NULL. */
66#define MMGR_U64 {MMGR_FK_U64, 0u, 0u, NULL}
67
68/** @brief Expands to an mmgr_field of kind MMGR_FK_I64, with width 0, bytes 0 and literal NULL. */
69#define MMGR_I64 {MMGR_FK_I64, 0u, 0u, NULL}
70
71/** @brief Expands to an mmgr_field of kind MMGR_FK_CH, with width 0, bytes 0 and literal NULL. */
72#define MMGR_CH {MMGR_FK_CH, 0u, 0u, NULL}
73
74/** @brief Expands to an mmgr_field of kind MMGR_FK_JSON, with width 0, bytes 0 and literal NULL. */
75#define MMGR_JSON {MMGR_FK_JSON, 0u, 0u, NULL}
76
77/** @brief Expands to an mmgr_field of kind MMGR_FK_XML, with width 0, bytes 0 and literal NULL. */
78#define MMGR_XML {MMGR_FK_XML, 0u, 0u, NULL}
79
80/**
81 * @brief Expands to an mmgr_field of kind MMGR_FK_END, with width 0, bytes 0 and literal NULL.
82 *
83 * @note numer_build stops at this field, and so every spec array needs one as its last entry.
84 */
85#define MMGR_END {MMGR_FK_END, 0u, 0u, NULL}
86
87/**
88 * @brief One value, tagged with the kind that selects which arm of as holds it.
89 *
90 * @note numer_build requires kind to equal the field's kind, and abandons the whole write when it does not.
91 * @note numer_emit takes width from here, where numer_build takes it from the field.
92 * @warning numer_emit_one reads the arm that kind names and no other. A value whose kind and
93 * filled arm disagree formats what that arm was left holding.
94 */
95typedef struct
96{
97 mmgr_fk kind; /**< Which arm of as holds the value. */
98 union {
99 const char *text; /**< Read for MMGR_FK_STR, MMGR_FK_JSON and MMGR_FK_XML [BORROWS]. */
100 uint32_t u32; /**< Read for MMGR_FK_U32 and MMGR_FK_DEC. */
101 uint64_t u64; /**< Read for MMGR_FK_U64, MMGR_FK_HEX and MMGR_FK_OCT. */
102 int64_t i64; /**< Read for MMGR_FK_I64. */
103 double real; /**< Read for MMGR_FK_G and MMGR_FK_FIX. */
104 char character; /**< Read for MMGR_FK_CH. */
105 } as; /**< The value, under the arm that kind names. */
106 uint8_t width; /**< Width numer_emit gives this value, or 0 to take the kind's default from s_kind. */
107} mmgr_fval;
108
109/**
110 * @brief Expands to an mmgr_fval of kind MMGR_FK_STR, with text_ in as.text and width 0.
111 *
112 * @param[in] text_ String placed in as.text [BORROWS].
113 */
114#define MMGR_VSTR(text_) {MMGR_FK_STR, {.text = (text_)}, 0u}
115
116/**
117 * @brief Expands to an mmgr_fval of kind MMGR_FK_U32, with value_ in as.u32 and width 0.
118 *
119 * @param[in] value_ Value placed in as.u32.
120 */
121#define MMGR_VU32(value_) {MMGR_FK_U32, {.u32 = (value_)}, 0u}
122
123/**
124 * @brief Expands to an mmgr_fval of kind MMGR_FK_U64, with value_ in as.u64 and width 0.
125 *
126 * @param[in] value_ Value placed in as.u64.
127 */
128#define MMGR_VU64(value_) {MMGR_FK_U64, {.u64 = (value_)}, 0u}
129
130/**
131 * @brief Expands to an mmgr_fval of kind MMGR_FK_I64, with value_ in as.i64 and width 0.
132 *
133 * @param[in] value_ Value placed in as.i64.
134 */
135#define MMGR_VI64(value_) {MMGR_FK_I64, {.i64 = (value_)}, 0u}
136
137/**
138 * @brief Expands to an mmgr_fval of kind MMGR_FK_DEC, with value_ in as.u32 and width 0.
139 *
140 * @param[in] value_ Value placed in as.u32.
141 */
142#define MMGR_VDEC(value_) {MMGR_FK_DEC, {.u32 = (value_)}, 0u}
143
144/**
145 * @brief Expands to an mmgr_fval of kind MMGR_FK_HEX, with value_ in as.u64 and width 0.
146 *
147 * @param[in] value_ Value placed in as.u64.
148 */
149#define MMGR_VHEX(value_) {MMGR_FK_HEX, {.u64 = (value_)}, 0u}
150
151/**
152 * @brief Expands to an mmgr_fval of kind MMGR_FK_OCT, with value_ in as.u64 and width 0.
153 *
154 * @param[in] value_ Value placed in as.u64.
155 */
156#define MMGR_VOCT(value_) {MMGR_FK_OCT, {.u64 = (value_)}, 0u}
157
158/**
159 * @brief Expands to an mmgr_fval of kind MMGR_FK_G, with value_ in as.real and width 0.
160 *
161 * @param[in] value_ Value placed in as.real.
162 */
163#define MMGR_VG(value_) {MMGR_FK_G, {.real = (value_)}, 0u}
164
165/**
166 * @brief Expands to an mmgr_fval of kind MMGR_FK_FIX, with value_ in as.real and width 0.
167 *
168 * @param[in] value_ Value placed in as.real.
169 */
170#define MMGR_VFIX(value_) {MMGR_FK_FIX, {.real = (value_)}, 0u}
171
172/**
173 * @brief Expands to an mmgr_fval of kind MMGR_FK_CH, with character_ in as.character and width 0.
174 *
175 * @param[in] character_ Character placed in as.character.
176 */
177#define MMGR_VCH(character_) {MMGR_FK_CH, {.character = (character_)}, 0u}
178
179/**
180 * @brief Expands to an mmgr_fval of kind MMGR_FK_JSON, with text_ in as.text and width 0.
181 *
182 * @param[in] text_ String placed in as.text [BORROWS].
183 */
184#define MMGR_VJSON(text_) {MMGR_FK_JSON, {.text = (text_)}, 0u}
185
186/**
187 * @brief Expands to an mmgr_fval of kind MMGR_FK_XML, with text_ in as.text and width 0.
188 *
189 * @param[in] text_ String placed in as.text [BORROWS].
190 */
191#define MMGR_VXML(text_) {MMGR_FK_XML, {.text = (text_)}, 0u}
192
193/**
194 * @brief Expands to an mmgr_fval of kind MMGR_FK_DEC, with value_ in as.u32 and width_ in width.
195 *
196 * @param[in] value_ Value placed in as.u32.
197 * @param[in] width_ Width placed in the width member.
198 * @note numer_emit uses that width. numer_build takes the width from the field instead.
199 */
200#define MMGR_VDECW(value_, width_) {MMGR_FK_DEC, {.u32 = (value_)}, (width_)}
201
202/**
203 * @brief Expands to an mmgr_fval of kind MMGR_FK_HEX, with value_ in as.u64 and width_ in width.
204 *
205 * @param[in] value_ Value placed in as.u64.
206 * @param[in] width_ Width placed in the width member.
207 * @note numer_emit uses that width. numer_build takes the width from the field instead.
208 */
209#define MMGR_VHEXW(value_, width_) {MMGR_FK_HEX, {.u64 = (value_)}, (width_)}
210
211/**
212 * @brief Expands to an mmgr_fval of kind MMGR_FK_OCT, with value_ in as.u64 and width_ in width.
213 *
214 * @param[in] value_ Value placed in as.u64.
215 * @param[in] width_ Width placed in the width member.
216 * @note numer_emit uses that width. numer_build takes the width from the field instead.
217 */
218#define MMGR_VOCTW(value_, width_) {MMGR_FK_OCT, {.u64 = (value_)}, (width_)}
219
220/**
221 * @brief Expands to an mmgr_fval of kind MMGR_FK_G, with value_ in as.real and width_ in width.
222 *
223 * @param[in] value_ Value placed in as.real.
224 * @param[in] width_ Width placed in the width member.
225 * @note numer_emit_one hands that width to mmgr_verba_g as VerbaFractioCfg::sig, the significant
226 * digits it keeps.
227 * @note numer_emit uses it. numer_build takes the width from the field instead.
228 */
229#define MMGR_VGW(value_, width_) {MMGR_FK_G, {.real = (value_)}, (width_)}
230
231/**
232 * @brief Expands to an mmgr_fval of kind MMGR_FK_FIX, with value_ in as.real and width_ in width.
233 *
234 * @param[in] value_ Value placed in as.real.
235 * @param[in] width_ Width placed in the width member.
236 * @note numer_emit_one hands that width to mmgr_verba_fixed as VerbaFractioCfg::decimals, the digits
237 * it writes after the point.
238 * @note numer_emit uses it. numer_build takes the width from the field instead.
239 */
240#define MMGR_VFIXW(value_, width_) {MMGR_FK_FIX, {.real = (value_)}, (width_)}
241
242/**
243 * @brief Arguments for the four numer calls.
244 *
245 * @note build and append read all six members. emit and emit_append leave spec alone.
246 * @note at is the cursor, as in verba. build and emit begin there and return where they finished, and therefore
247 * a run of writes threads the cursor instead of measuring the text again between each. Leave
248 * it unset and a call starts at the first byte, as a single write wants.
249 */
250typedef struct
251{
252 char *const out; /**< Destination buffer [BORROWS]. */
253 const size_t cap; /**< Bytes available in out. */
254 const size_t at; /**< Offset to begin writing at; 0 for the first write. */
255 const mmgr_field *const spec; /**< Field list, ending at MMGR_FK_END [BORROWS]. */
256 const mmgr_fval *const vals; /**< Values to place into the fields [BORROWS]. */
257 const size_t nvals; /**< Values in vals. */
258} NumerosCfg;
259
260/**
261 * @brief Type of the numer dispatch table.
262 *
263 * @note EMBED_TABLE_LAYOUT asserts the four members sit at consecutive EMBED_FUNCTION_POINTER_BYTES offsets, with
264 * nothing else.
265 * @note build and emit begin at NumerosCfg::at, which is out's first byte while the caller leaves it
266 * unset. The two append members begin past the text already there.
267 */
268typedef struct
269{
270 size_t (*build)(const NumerosCfg *args); /**< Writes a spec and its values. */
271 size_t (*append)(const NumerosCfg *args); /**< Writes a spec and its values after the text in out. */
272 size_t (*emit)(const NumerosCfg *args); /**< Writes values, with no field list. */
273 size_t (*emit_append)(const NumerosCfg *args); /**< Writes values after the text in out, with no field list. */
275EMBED_TABLE_LAYOUT(NumerosScriboNs, build, append, emit, emit_append);
276
277/**
278 * @brief Writes args->spec and args->vals into args->out, starting at args->at.
279 *
280 * @param[in] args Buffer, capacity, the field list and the values [BORROWS].
281 * @return Length of the whole string in args->out, not counting its terminator, or 0 when
282 * nothing was written.
283 * @note An MMGR_FK_LIT field writes its own text. Every other field consumes the next value in
284 * args->vals.
285 * @note Returns 0 and puts the terminator back at args->at when a value is missing, when a kind differs,
286 * or when values are left over, which leaves the text written before this call whole.
287 * @warning args->spec must reach an MMGR_FK_END field, and args->vals must hold args->nvals values.
288 */
289size_t mmgr_numer_build(const NumerosCfg *args);
290
291/**
292 * @brief Writes args->spec and args->vals into args->out after the string already there.
293 *
294 * @param[in] args Buffer, capacity, the field list and the values [BORROWS].
295 * @return Length of the whole string in args->out, or 0 when nothing was added.
296 * @note Carries on at args->at when the caller threaded one, and measures the existing string with
297 * cellul.len only when args->at is 0. Either way the build gets the whole of args->cap and
298 * that offset.
299 * @note Puts the terminator back at the offset it began from and returns 0 when the build writes nothing.
300 * @warning args->spec must reach an MMGR_FK_END field, and args->out must hold a terminated string when
301 * args->at is 0, since that is the only time the length is measured.
302 */
303size_t mmgr_numer_append(const NumerosCfg *args);
304
305/**
306 * @brief Writes args->vals into args->out, starting at args->at and reading no field list.
307 *
308 * @param[in] args Buffer, capacity and the values [BORROWS].
309 * @return Length of the whole string in args->out, not counting its terminator, or 0 when
310 * nothing was written.
311 * @note Each value carries its own width, and no kind is matched, since there are no fields to match against.
312 * @warning args->vals must hold args->nvals values.
313 * @warning A value of kind MMGR_FK_END or MMGR_FK_LIT formats nothing, which abandons the write and returns 0.
314 */
315size_t mmgr_numer_emit(const NumerosCfg *args);
316
317/**
318 * @brief Writes args->vals into args->out after the string already there, reading no field list.
319 *
320 * @param[in] args Buffer, capacity and the values [BORROWS].
321 * @return Length of the whole string in args->out, or 0 when nothing was added.
322 * @note Carries on at args->at when the caller threaded one, and measures the existing string with
323 * cellul.len only when args->at is 0. Either way the emit gets the whole of args->cap and
324 * that offset.
325 * @note Puts the terminator back at the offset it began from and returns 0 when the emit writes nothing.
326 * @warning args->vals must hold args->nvals values, and args->out must hold a terminated string when
327 * args->at is 0, since that is the only time the length is measured.
328 */
330
331/**
332 * @brief Dispatch table instance named numer.
333 *
334 * @note Each member calls the matching mmgr_numer_ function, one to one.
335 * @note mmgr_numer_append reaches build through this table, and mmgr_numer_emit_append reaches emit.
336 */
337EMBED_TABLE_STORAGE NumerosScriboNs numer EMBED_UNUSED = {
338 .build = mmgr_numer_build,
339 .append = mmgr_numer_append,
340 .emit = mmgr_numer_emit,
341 .emit_append = mmgr_numer_emit_append,
342};
343
344EMBED_END_DECLS
345
346#endif
size_t mmgr_numer_emit(const NumerosCfg *args)
Writes args->vals into args->out, starting at args->at and reading no field list.
size_t mmgr_numer_emit_append(const NumerosCfg *args)
Writes args->vals into args->out after the string already there, reading no field list.
mmgr_fk
What one field formats, and which mmgr_fval arm it reads.
@ MMGR_FK_OCT
@ MMGR_FK_JSON
@ MMGR_FK_LIT
@ MMGR_FK_I64
@ MMGR_FK_HEX
@ MMGR_FK_FIX
@ MMGR_FK_STR
@ MMGR_FK_END
@ MMGR_FK_G
@ MMGR_FK_U32
@ MMGR_FK_U64
@ MMGR_FK_XML
@ MMGR_FK_DEC
@ MMGR_FK_CH
size_t mmgr_numer_build(const NumerosCfg *args)
Writes args->spec and args->vals into args->out, starting at args->at.
static const NumerosScriboNs numer
Dispatch table instance named numer.
size_t mmgr_numer_append(const NumerosCfg *args)
Writes args->spec and args->vals into args->out after the string already there.
Arguments for the four numer calls.
const size_t cap
const size_t at
const mmgr_fval *const vals
const size_t nvals
const mmgr_field *const spec
char *const out
Type of the numer dispatch table.
size_t(*) emit_append(const NumerosCfg *args)
size_t(*) append(const NumerosCfg *args)
size_t(*) build(const NumerosCfg *args)
size_t(*) emit(const NumerosCfg *args)
One field of a spec.
const char * literal
uint8_t width
uint16_t bytes
One value, tagged with the kind that selects which arm of as holds it.
uint8_t width
mmgr_fk kind
Text and number formatting, the limits, and one table per kind of thing written.