Graphviz 13.1.3~dev.20250829.0113
Loading...
Searching...
No Matches
agxbuf.h
Go to the documentation of this file.
1
5/*************************************************************************
6 * Copyright (c) 2011 AT&T Intellectual Property
7 * All rights reserved. This program and the accompanying materials
8 * are made available under the terms of the Eclipse Public License v1.0
9 * which accompanies this distribution, and is available at
10 * https://www.eclipse.org/legal/epl-v10.html
11 *
12 * Contributors: Details at https://graphviz.org
13 *************************************************************************/
14
15#pragma once
16
17#include <assert.h>
18#include <limits.h>
19#include <stdarg.h>
20#include <stdbool.h>
21#include <stddef.h>
22#include <stdint.h>
23#include <stdio.h>
24#include <string.h>
25#include <util/alloc.h>
26#include <util/unused.h>
27
34
54typedef struct {
55 union {
56 struct {
57 char *buf;
58 size_t size;
59 size_t capacity;
60 char padding[sizeof(size_t) - 1];
61 unsigned char
63 };
64 char store[sizeof(char *) + sizeof(size_t) * 3 -
65 1];
67 };
68} agxbuf;
69
70static inline bool agxbuf_is_inline(const agxbuf *xb) {
71 assert((xb->located == AGXBUF_ON_HEAP || xb->located <= sizeof(xb->store)) &&
72 "corrupted agxbuf type");
73 return xb->located < AGXBUF_ON_HEAP;
74}
75
77static inline void agxbfree(agxbuf *xb) {
78 if (xb->located == AGXBUF_ON_HEAP)
79 free(xb->buf);
80}
81
83static inline char *agxbstart(agxbuf *xb) {
84 return agxbuf_is_inline(xb) ? xb->store : xb->buf;
85}
86
88static inline size_t agxblen(const agxbuf *xb) {
89 if (agxbuf_is_inline(xb)) {
90 return xb->located - AGXBUF_INLINE_SIZE_0;
91 }
92 return xb->size;
93}
94
102static inline size_t agxbsizeof(const agxbuf *xb) {
103 if (agxbuf_is_inline(xb)) {
104 return sizeof(xb->store);
105 }
106 return xb->capacity;
107}
108
110static inline int agxbpop(agxbuf *xb) {
111
112 size_t len = agxblen(xb);
113 if (len == 0) {
114 return -1;
115 }
116
117 if (agxbuf_is_inline(xb)) {
118 assert(xb->located > AGXBUF_INLINE_SIZE_0);
119 int c = xb->store[len - 1];
120 --xb->located;
121 return c;
122 }
123
124 int c = xb->buf[xb->size - 1];
125 --xb->size;
126 return c;
127}
128
130static inline void agxbmore(agxbuf *xb, size_t ssz) {
131 size_t cnt = 0; // current no. of characters in buffer
132 size_t size = 0; // current buffer size
133 size_t nsize = 0; // new buffer size
134 char *nbuf; // new buffer
135
136 size = agxbsizeof(xb);
137 nsize = size == 0 ? BUFSIZ : (2 * size);
138 if (size + ssz > nsize)
139 nsize = size + ssz;
140 cnt = agxblen(xb);
141
142 if (xb->located == AGXBUF_ON_HEAP) {
143 nbuf = (char *)gv_recalloc(xb->buf, size, nsize, sizeof(char));
144 } else {
145 nbuf = (char *)gv_calloc(nsize, sizeof(char));
146 memcpy(nbuf, xb->store, cnt);
147 xb->size = cnt;
148 }
149 xb->buf = nbuf;
150 xb->capacity = nsize;
152}
153
155static inline char *agxbnext(agxbuf *xb) {
156 size_t len = agxblen(xb);
157 return agxbuf_is_inline(xb) ? &xb->store[len] : &xb->buf[len];
158}
159
161static inline int vagxbprint(agxbuf *xb, const char *fmt, va_list ap) {
162 size_t size;
163 int result;
164
165 // determine how many bytes we need to print
166 {
167 va_list ap2;
168 int rc;
169 va_copy(ap2, ap);
170 rc = vsnprintf(NULL, 0, fmt, ap2);
171 va_end(ap2);
172 if (rc < 0) {
173 va_end(ap);
174 return rc;
175 }
176 size = (size_t)rc + 1; // account for NUL terminator
177 }
178
179 // should we use double buffering?
180 bool use_stage = false;
181
182 // do we need to expand the buffer?
183 {
184 size_t unused_space = agxbsizeof(xb) - agxblen(xb);
185 if (unused_space < size) {
186 size_t extra = size - unused_space;
187 if (agxbuf_is_inline(xb) && extra == 1) {
188 // The content is currently stored inline, but this print will push it
189 // over into being heap-allocated by a single byte. This last byte is a
190 // '\0' that `vsnprintf` unavoidably writes but we do not need. So lets
191 // avoid this by printing to an intermediate, larger buffer, and then
192 // copying the content minus the trailing '\0' to the final destination.
193 use_stage = true;
194 } else {
195 agxbmore(xb, extra);
196 }
197 }
198 }
199
200 // a buffer one byte larger than inline storage to fit the trailing '\0'
201 char stage[sizeof(xb->store) + 1] = {0};
202 assert(!use_stage || size <= sizeof(stage));
203
204 // we can now safely print into the buffer
205 char *dst = use_stage ? stage : agxbnext(xb);
206 result = vsnprintf(dst, size, fmt, ap);
207 assert(result == (int)(size - 1) || result < 0);
208 if (result > 0) {
209 if (agxbuf_is_inline(xb)) {
210 assert(result <= (int)UCHAR_MAX);
211 if (use_stage) {
212 memcpy(agxbnext(xb), stage, (size_t)result);
213 }
214 xb->located += (unsigned char)result;
215 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
216 } else {
217 assert(!use_stage);
218 xb->size += (size_t)result;
219 }
220 }
221
222 return result;
223}
224
225/* support for extra API misuse warnings if available */
226#ifdef __GNUC__
227#define PRINTF_LIKE(index, first) __attribute__((format(printf, index, first)))
228#else
229#define PRINTF_LIKE(index, first) /* nothing */
230#endif
231
233static inline PRINTF_LIKE(2, 3) int agxbprint(agxbuf *xb, const char *fmt,
234 ...) {
235 va_list ap;
236 int result;
237
238 va_start(ap, fmt);
239
240 result = vagxbprint(xb, fmt, ap);
241
242 va_end(ap);
243 return result;
244}
245
246#undef PRINTF_LIKE
247
249static inline size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz) {
250 if (ssz == 0) {
251 return 0;
252 }
253 if (ssz > agxbsizeof(xb) - agxblen(xb))
254 agxbmore(xb, ssz);
255 size_t len = agxblen(xb);
256 if (agxbuf_is_inline(xb)) {
257 memcpy(&xb->store[len], s, ssz);
258 assert(ssz <= UCHAR_MAX);
259 xb->located += (unsigned char)ssz;
260 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
261 } else {
262 memcpy(&xb->buf[len], s, ssz);
263 xb->size += ssz;
264 }
265 return ssz;
266}
267
269static inline size_t agxbput(agxbuf *xb, const char *s) {
270 size_t ssz = strlen(s);
271
272 return agxbput_n(xb, s, ssz);
273}
274
276static inline int agxbputc(agxbuf *xb, char c) {
277 if (agxblen(xb) >= agxbsizeof(xb)) {
278 agxbmore(xb, 1);
279 }
280 size_t len = agxblen(xb);
281 if (agxbuf_is_inline(xb)) {
282 xb->store[len] = c;
283 ++xb->located;
284 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
285 } else {
286 xb->buf[len] = c;
287 ++xb->size;
288 }
289 return 0;
290}
291
293static inline void agxbclear(agxbuf *xb) {
294 if (agxbuf_is_inline(xb)) {
296 } else {
297 xb->size = 0;
298 }
299}
300
301/* Null-terminates buffer; resets and returns pointer to data. The buffer is
302 * still associated with the agxbuf and will be overwritten on the next, e.g.,
303 * agxbput. If you want to retrieve and disassociate the buffer, use agxbdisown
304 * instead.
305 */
306static inline WUR char *agxbuse(agxbuf *xb) {
307 if (!agxbuf_is_inline(xb) || agxblen(xb) != sizeof(xb->store)) {
308 (void)agxbputc(xb, '\0');
309 } else {
310 // we can skip explicitly null-terminating the buffer because `agxbclear`
311 // resets the `xb->located` byte such that it naturally forms a terminator
312 assert(AGXBUF_INLINE_SIZE_0 == '\0');
313 }
314
315 agxbclear(xb);
316 return agxbstart(xb);
317}
318
319/* Disassociate the backing buffer from this agxbuf and return it. The buffer is
320 * NUL terminated before being returned. If the agxbuf is using stack memory,
321 * this will first copy the data to a new heap buffer to then return. If you
322 * want to temporarily access the string in the buffer, but have it overwritten
323 * and reused the next time, e.g., agxbput is called, use agxbuse instead of
324 * agxbdisown.
325 */
326static inline char *agxbdisown(agxbuf *xb) {
327 char *buf;
328
329 if (agxbuf_is_inline(xb)) {
330 // the string lives in `store`, so we need to copy its contents to heap
331 // memory
332 buf = gv_strndup(xb->store, agxblen(xb));
333 } else {
334 // the buffer is already dynamically allocated, so terminate it and then
335 // take it as-is
336 agxbputc(xb, '\0');
337 buf = xb->buf;
338 }
339
340 // reset xb to a state where it is usable
341 memset(xb, 0, sizeof(*xb));
342
343 return buf;
344}
345
363static inline void agxbuf_trim_zeros(agxbuf *xb) {
364
365 // find last period
366 char *start = agxbstart(xb);
367 size_t period;
368 for (period = agxblen(xb) - 1;; --period) {
369 if (period == SIZE_MAX) {
370 // we searched the entire string and did not find a period
371 return;
372 }
373 if (start[period] == '.') {
374 break;
375 }
376 }
377
378 // truncate any “0”s that provide no information
379 for (size_t follower = agxblen(xb) - 1;; --follower) {
380 if (follower == period || start[follower] == '0') {
381 // truncate this character
382 if (agxbuf_is_inline(xb)) {
383 assert(xb->located > AGXBUF_INLINE_SIZE_0);
384 --xb->located;
385 } else {
386 --xb->size;
387 }
388 if (follower == period) {
389 break;
390 }
391 } else {
392 return;
393 }
394 }
395
396 // is the remainder we have left not “-0”?
397 const size_t len = agxblen(xb);
398 if (len < 2 || start[len - 2] != '-' || start[len - 1] != '0') {
399 return;
400 }
401
402 // turn “-0” into “0”
403 start[len - 2] = '0';
404 if (agxbuf_is_inline(xb)) {
405 assert(xb->located > AGXBUF_INLINE_SIZE_0);
406 --xb->located;
407 } else {
408 --xb->size;
409 }
410}
static int agxbpop(agxbuf *xb)
removes last character added, if any
Definition agxbuf.h:110
static size_t agxbsizeof(const agxbuf *xb)
Definition agxbuf.h:102
static void agxbuf_trim_zeros(agxbuf *xb)
Definition agxbuf.h:363
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:77
static size_t agxbput(agxbuf *xb, const char *s)
append string s into xb
Definition agxbuf.h:269
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:249
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:233
static char * agxbnext(agxbuf *xb)
next position for writing
Definition agxbuf.h:155
static void agxbclear(agxbuf *xb)
resets pointer to data
Definition agxbuf.h:293
static int vagxbprint(agxbuf *xb, const char *fmt, va_list ap)
vprintf-style output to an agxbuf
Definition agxbuf.h:161
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:306
static void agxbmore(agxbuf *xb, size_t ssz)
expand buffer to hold at least ssz more bytes
Definition agxbuf.h:130
static size_t agxblen(const agxbuf *xb)
return number of characters currently stored
Definition agxbuf.h:88
static char * agxbstart(agxbuf *xb)
return pointer to beginning of buffer
Definition agxbuf.h:83
static bool agxbuf_is_inline(const agxbuf *xb)
Definition agxbuf.h:70
#define PRINTF_LIKE(index, first)
Definition agxbuf.h:229
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:276
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:326
agxbuf_loc_t
a description of where a buffer is located
Definition agxbuf.h:29
@ AGXBUF_ON_HEAP
Definition agxbuf.h:31
@ AGXBUF_INLINE_SIZE_0
Definition agxbuf.h:30
Memory allocation wrappers that exit on failure.
static void * gv_recalloc(void *ptr, size_t old_nmemb, size_t new_nmemb, size_t size)
Definition alloc.h:73
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static char * gv_strndup(const char *original, size_t length)
Definition alloc.h:114
static double len(glCompPoint p)
Definition glutils.c:136
void free(void *)
#define SIZE_MAX
Definition gmlscan.c:347
node NULL
Definition grammar.y:181
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:196
static int store(segment_t *seg, int first, pointf *pts)
Definition partition.c:120
char store[sizeof(char *)+sizeof(size_t) *3 - 1]
Definition agxbuf.h:65
char * buf
start of buffer
Definition agxbuf.h:57
size_t size
number of characters in the buffer
Definition agxbuf.h:58
unsigned char located
where does the backing memory for this buffer live?
Definition agxbuf.h:62
size_t capacity
available bytes in the buffer
Definition agxbuf.h:59
Definition grammar.c:90
abstraction for squashing compiler warnings for unused symbols
#define WUR
Definition unused.h:43