ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
frame.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file frame.cpp
6 * @brief The one frame engine. Walks a pc_field spec, pulls one argument per valued field.
7 *
8 * Deliberately thin: every conversion is an already-tested pc_sb appender, so this file adds a
9 * dispatch loop and the fail-closed contract, not new formatting logic. That is the point of the
10 * spec - the number of places that can get a conversion wrong stays at one no matter how many
11 * frames the library declares.
12 */
13
16
17#ifndef PC_FRAME_SCAN_LITERALS
18#define PC_FRAME_SCAN_LITERALS 0 // 1 = find each literal length at runtime instead of reading it from the spec
19#endif
20
21// A dispatch loop over a table with no secrets, and the level it would otherwise inherit is what
22// stops the appenders inlining.
24
25size_t pc_frame_vbuild(char *out, size_t cap, const pc_field *spec, va_list ap)
26{
27 if (!out || cap == 0 || !spec)
28 {
29 return 0;
30 }
31 pc_sb b = {out, cap, 0, true};
32 for (const pc_field *f = spec; f->kind != PC_FK_END; f++)
33 {
34 switch (f->kind)
35 {
36 case PC_FK_LIT:
37#if PC_FRAME_SCAN_LITERALS
38 pc_sb_put(&b, f->lit);
39#else
40 // the spec carries the length; scanning for the NUL would rediscover it every call
41 pc_sb_put_n(&b, f->lit, f->len);
42#endif
43 break;
44 case PC_FK_STR: {
45 const char *s = va_arg(ap, const char *);
46 pc_sb_put(&b, s ? s : "");
47 break;
48 }
49 case PC_FK_U32:
50 pc_sb_u32(&b, va_arg(ap, uint32_t));
51 break;
52 case PC_FK_U64:
53 pc_sb_u64(&b, va_arg(ap, uint64_t));
54 break;
55 case PC_FK_I64:
56 pc_sb_i64(&b, va_arg(ap, int64_t));
57 break;
58 case PC_FK_DEC:
59 pc_sb_u32w(&b, va_arg(ap, uint32_t), f->width);
60 break;
61 case PC_FK_HEX:
62 pc_sb_hex(&b, va_arg(ap, uint64_t), f->width ? f->width : 1);
63 break;
64 case PC_FK_OCT:
65 pc_sb_uint(&b, va_arg(ap, uint64_t), 8, f->width ? f->width : 1);
66 break;
67 case PC_FK_G:
68 pc_sb_g(&b, va_arg(ap, double), f->width ? f->width : 6);
69 break;
70 case PC_FK_FIX:
71 pc_sb_fixed(&b, va_arg(ap, double), f->width);
72 break;
73 case PC_FK_CH:
74 // char promotes to int through the ellipsis, so it must be read back as int.
75 pc_sb_ch(&b, (char)va_arg(ap, int));
76 break;
77 case PC_FK_JSON:
78 pc_sb_json(&b, va_arg(ap, const char *));
79 break;
80 case PC_FK_XML:
81 pc_sb_xml(&b, va_arg(ap, const char *));
82 break;
83 default:
84 // An unknown opcode means the spec and this engine disagree; refuse rather than
85 // emit a frame that is missing a field and looks well-formed.
86 out[0] = '\0';
87 return 0;
88 }
89 }
90 size_t n = pc_sb_finish(&b);
91 if (n == 0)
92 {
93 // Did not fit (or was empty). Leave a valid, empty C string either way: callers that
94 // ignore the return must never read stale bytes or an unterminated buffer.
95 out[0] = '\0';
96 }
97 return n;
98}
99
100size_t pc_frame_build(char *out, size_t cap, const pc_field *spec, ...)
101{
102 va_list ap;
103 va_start(ap, spec);
104 size_t n = pc_frame_vbuild(out, cap, spec, ap);
105 va_end(ap);
106 return n;
107}
108
109size_t pc_frame_append(char *out, size_t cap, const pc_field *spec, ...)
110{
111 if (!out || cap == 0 || !spec)
112 {
113 return 0;
114 }
115 size_t used = strnlen(out, cap);
116 if (used >= cap)
117 {
118 return 0;
119 }
120 va_list ap;
121 va_start(ap, spec);
122 size_t n = pc_frame_vbuild(out + used, cap - used, spec, ap);
123 va_end(ap);
124 if (n == 0)
125 {
126 out[used] = '\0'; // rewind: the frame is added whole or not at all
127 return 0;
128 }
129 return used + n;
130}
size_t pc_frame_build(char *out, size_t cap, const pc_field *spec,...)
Build spec into out (capacity cap), taking one variadic argument per valued field.
Definition frame.cpp:100
size_t pc_frame_append(char *out, size_t cap, const pc_field *spec,...)
Append spec to the NUL-terminated contents already in out.
Definition frame.cpp:109
PC_OPTIMIZE_O2 size_t pc_frame_vbuild(char *out, size_t cap, const pc_field *spec, va_list ap)
va_list form, for a caller that already has one.
Definition frame.cpp:25
Declarative frame builder: a frame is a static table of typed fields, built by one engine.
@ PC_FK_G
double, printf %.<width>g (width 0 means 6)
Definition frame.h:64
@ PC_FK_OCT
uint64_t, octal zero-padded to width
Definition frame.h:63
@ PC_FK_JSON
const char *, emitted as a quoted JSON string literal
Definition frame.h:67
@ PC_FK_LIT
literal text from lit; takes no argument
Definition frame.h:56
@ PC_FK_CH
char
Definition frame.h:66
@ PC_FK_HEX
uint64_t, lowercase hex zero-padded to width
Definition frame.h:62
@ PC_FK_FIX
double, printf %.<width>f
Definition frame.h:65
@ PC_FK_I64
int64_t, signed decimal
Definition frame.h:60
@ PC_FK_DEC
uint32_t, decimal zero-padded to width
Definition frame.h:61
@ PC_FK_U32
uint32_t, plain decimal
Definition frame.h:58
@ PC_FK_U64
uint64_t, plain decimal
Definition frame.h:59
@ PC_FK_END
terminator; takes no argument
Definition frame.h:55
@ PC_FK_STR
const char * (NULL renders as empty)
Definition frame.h:57
@ PC_FK_XML
const char *, XML-escaped
Definition frame.h:68
Build one file at a named optimization level instead of the framework's.
#define PC_OPTIMIZE_O2
Build this file at optimization level 2, overriding the framework's size-optimized level.
Definition speed_opt.h:43
void pc_sb_g(pc_sb *b, double v, unsigned sig)
Append v with sig significant digits, choosing fixed or scientific form - the printf "%....
Definition strbuf.h:437
void pc_sb_fixed(pc_sb *b, double v, unsigned decimals)
Append v with exactly decimals digits after the point (printf "%.<decimals>f").
Definition strbuf.h:565
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_uint(pc_sb *b, uint64_t v, unsigned base, unsigned min_digits)
Append v in base (10 or 16), left-padded with '0' to at least min_digits.
Definition strbuf.h:207
void pc_sb_xml(pc_sb *b, const char *s)
Append s XML-escaped (& < > "); a NULL s appends nothing.
Definition strbuf.h:143
void pc_sb_hex(pc_sb *b, uint64_t v, unsigned min_digits)
Append v as lowercase hex, zero-padded to at least min_digits (printf "%0Nx").
Definition strbuf.h:297
void pc_sb_put_n(pc_sb *b, const char *s, size_t sl)
Append sl bytes of s - the primitive the others build on.
Definition strbuf.h:44
void pc_sb_ch(pc_sb *b, char c)
Append a single character.
Definition strbuf.h:186
void pc_sb_u64(pc_sb *b, uint64_t v)
Append v as decimal (64-bit).
Definition strbuf.h:309
void pc_sb_u32w(pc_sb *b, uint32_t v, unsigned min_digits)
Append v as decimal, zero-padded to at least min_digits (printf "%0Nu").
Definition strbuf.h:291
void pc_sb_i64(pc_sb *b, int64_t v)
Append v as signed decimal (64-bit), with a leading '-' when negative.
Definition strbuf.h:315
void pc_sb_json(pc_sb *b, const char *s)
Append s as a JSON string literal: double-quoted, with "</tt> and <tt>\</tt> backslash-escaped....
Definition strbuf.h:620
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Definition strbuf.h:303
One field of a frame. Frames are static const pc_field[], so they live in rodata.
Definition frame.h:80
uint8_t kind
a pc_fk
Definition frame.h:81
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30
size_t len
Definition strbuf.h:33