Graphviz 14.1.1~dev.20251208.1034
Loading...
Searching...
No Matches
agxbuf.h
Go to the documentation of this file.
1
25/*************************************************************************
26 * Copyright (c) 2011 AT&T Intellectual Property
27 * All rights reserved. This program and the accompanying materials
28 * are made available under the terms of the Eclipse Public License v1.0
29 * which accompanies this distribution, and is available at
30 * https://www.eclipse.org/legal/epl-v10.html
31 *
32 * Contributors: Details at https://graphviz.org
33 *************************************************************************/
34
35#pragma once
36
37#include <assert.h>
38#include <limits.h>
39#include <stdarg.h>
40#include <stdbool.h>
41#include <stddef.h>
42#include <stdint.h>
43#include <stdio.h>
44#include <string.h>
45#include <util/alloc.h>
46#include <util/unused.h>
47
54
74typedef struct {
75 union {
76 struct {
77 char *buf;
78 size_t size;
79 size_t capacity;
80 char padding[sizeof(size_t) - 1];
81 unsigned char
83 };
84 char store[sizeof(char *) + sizeof(size_t) * 3 -
85 1];
87 };
88} agxbuf;
89
90static inline bool agxbuf_is_inline(const agxbuf *xb) {
91 assert((xb->located == AGXBUF_ON_HEAP || xb->located <= sizeof(xb->store)) &&
92 "corrupted agxbuf type");
93 return xb->located < AGXBUF_ON_HEAP;
94}
95
97static inline void agxbfree(agxbuf *xb) {
98 if (xb->located == AGXBUF_ON_HEAP)
99 free(xb->buf);
100}
101
103static inline char *agxbstart(agxbuf *xb) {
104 return agxbuf_is_inline(xb) ? xb->store : xb->buf;
105}
106
108static inline size_t agxblen(const agxbuf *xb) {
109 if (agxbuf_is_inline(xb)) {
110 return xb->located - AGXBUF_INLINE_SIZE_0;
111 }
112 return xb->size;
113}
114
122static inline size_t agxbsizeof(const agxbuf *xb) {
123 if (agxbuf_is_inline(xb)) {
124 return sizeof(xb->store);
125 }
126 return xb->capacity;
127}
128
130static inline int agxbpop(agxbuf *xb) {
131
132 size_t len = agxblen(xb);
133 if (len == 0) {
134 return -1;
135 }
136
137 if (agxbuf_is_inline(xb)) {
138 assert(xb->located > AGXBUF_INLINE_SIZE_0);
139 int c = xb->store[len - 1];
140 --xb->located;
141 return c;
142 }
143
144 int c = xb->buf[xb->size - 1];
145 --xb->size;
146 return c;
147}
148
150static inline void agxbmore(agxbuf *xb, size_t ssz) {
151 size_t cnt = 0; // current no. of characters in buffer
152 size_t size = 0; // current buffer size
153 size_t nsize = 0; // new buffer size
154 char *nbuf; // new buffer
155
156 size = agxbsizeof(xb);
157 nsize = size == 0 ? BUFSIZ : (2 * size);
158 if (size + ssz > nsize)
159 nsize = size + ssz;
160 cnt = agxblen(xb);
161
162 if (xb->located == AGXBUF_ON_HEAP) {
163 nbuf = (char *)gv_recalloc(xb->buf, size, nsize, sizeof(char));
164 } else {
165 nbuf = (char *)gv_calloc(nsize, sizeof(char));
166 memcpy(nbuf, xb->store, cnt);
167 xb->size = cnt;
168 }
169 xb->buf = nbuf;
170 xb->capacity = nsize;
172}
173
175static inline char *agxbnext(agxbuf *xb) {
176 size_t len = agxblen(xb);
177 return agxbuf_is_inline(xb) ? &xb->store[len] : &xb->buf[len];
178}
179
181static inline int vagxbprint(agxbuf *xb, const char *fmt, va_list ap) {
182 size_t size;
183 int result;
184
185 // determine how many bytes we need to print
186 {
187 va_list ap2;
188 int rc;
189 va_copy(ap2, ap);
190 rc = vsnprintf(NULL, 0, fmt, ap2);
191 va_end(ap2);
192 if (rc < 0) {
193 return rc;
194 }
195 size = (size_t)rc + 1; // account for NUL terminator
196 }
197
198 // should we use double buffering?
199 bool use_stage = false;
200
201 // do we need to expand the buffer?
202 {
203 size_t unused_space = agxbsizeof(xb) - agxblen(xb);
204 if (unused_space < size) {
205 size_t extra = size - unused_space;
206 if (agxbuf_is_inline(xb) && extra == 1) {
207 // The content is currently stored inline, but this print will push it
208 // over into being heap-allocated by a single byte. This last byte is a
209 // '\0' that `vsnprintf` unavoidably writes but we do not need. So lets
210 // avoid this by printing to an intermediate, larger buffer, and then
211 // copying the content minus the trailing '\0' to the final destination.
212 use_stage = true;
213 } else {
214 agxbmore(xb, extra);
215 }
216 }
217 }
218
219 // a buffer one byte larger than inline storage to fit the trailing '\0'
220 char stage[sizeof(xb->store) + 1] = {0};
221 assert(!use_stage || size <= sizeof(stage));
222
223 // we can now safely print into the buffer
224 char *dst = use_stage ? stage : agxbnext(xb);
225 result = vsnprintf(dst, size, fmt, ap);
226 assert(result == (int)(size - 1) || result < 0);
227 if (result > 0) {
228 if (agxbuf_is_inline(xb)) {
229 assert(result <= (int)UCHAR_MAX);
230 if (use_stage) {
231 memcpy(agxbnext(xb), stage, (size_t)result);
232 }
233 xb->located += (unsigned char)result;
234 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
235 } else {
236 assert(!use_stage);
237 xb->size += (size_t)result;
238 }
239 }
240
241 return result;
242}
243
244/* support for extra API misuse warnings if available */
245#ifdef __GNUC__
246#define PRINTF_LIKE(index, first) __attribute__((format(printf, index, first)))
247#else
248#define PRINTF_LIKE(index, first) /* nothing */
249#endif
250
252static inline PRINTF_LIKE(2, 3) int agxbprint(agxbuf *xb, const char *fmt,
253 ...) {
254 va_list ap;
255 int result;
256
257 va_start(ap, fmt);
258
259 result = vagxbprint(xb, fmt, ap);
260
261 va_end(ap);
262 return result;
263}
264
265#undef PRINTF_LIKE
266
268static inline size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz) {
269 if (ssz == 0) {
270 return 0;
271 }
272 if (ssz > agxbsizeof(xb) - agxblen(xb))
273 agxbmore(xb, ssz);
274 size_t len = agxblen(xb);
275 if (agxbuf_is_inline(xb)) {
276 memcpy(&xb->store[len], s, ssz);
277 assert(ssz <= UCHAR_MAX);
278 xb->located += (unsigned char)ssz;
279 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
280 } else {
281 memcpy(&xb->buf[len], s, ssz);
282 xb->size += ssz;
283 }
284 return ssz;
285}
286
288static inline size_t agxbput(agxbuf *xb, const char *s) {
289 size_t ssz = strlen(s);
290
291 return agxbput_n(xb, s, ssz);
292}
293
295static inline int agxbputc(agxbuf *xb, char c) {
296 if (agxblen(xb) >= agxbsizeof(xb)) {
297 agxbmore(xb, 1);
298 }
299 size_t len = agxblen(xb);
300 if (agxbuf_is_inline(xb)) {
301 xb->store[len] = c;
302 ++xb->located;
303 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
304 } else {
305 xb->buf[len] = c;
306 ++xb->size;
307 }
308 return 0;
309}
310
312static inline void agxbclear(agxbuf *xb) {
313 if (agxbuf_is_inline(xb)) {
315 } else {
316 xb->size = 0;
317 }
318}
319
320/* Null-terminates buffer; resets and returns pointer to data. The buffer is
321 * still associated with the agxbuf and will be overwritten on the next, e.g.,
322 * agxbput. If you want to retrieve and disassociate the buffer, use agxbdisown
323 * instead.
324 */
325static inline WUR char *agxbuse(agxbuf *xb) {
326 if (!agxbuf_is_inline(xb) || agxblen(xb) != sizeof(xb->store)) {
327 (void)agxbputc(xb, '\0');
328 } else {
329 // we can skip explicitly null-terminating the buffer because `agxbclear`
330 // resets the `xb->located` byte such that it naturally forms a terminator
331 assert(AGXBUF_INLINE_SIZE_0 == '\0');
332 }
333
334 agxbclear(xb);
335 return agxbstart(xb);
336}
337
338/* Disassociate the backing buffer from this agxbuf and return it. The buffer is
339 * NUL terminated before being returned. If the agxbuf is using stack memory,
340 * this will first copy the data to a new heap buffer to then return. If you
341 * want to temporarily access the string in the buffer, but have it overwritten
342 * and reused the next time, e.g., agxbput is called, use agxbuse instead of
343 * agxbdisown.
344 */
345static inline char *agxbdisown(agxbuf *xb) {
346 char *buf;
347
348 if (agxbuf_is_inline(xb)) {
349 // the string lives in `store`, so we need to copy its contents to heap
350 // memory
351 buf = gv_strndup(xb->store, agxblen(xb));
352 } else {
353 // the buffer is already dynamically allocated, so terminate it and then
354 // take it as-is
355 agxbputc(xb, '\0');
356 buf = xb->buf;
357 }
358
359 // reset xb to a state where it is usable
360 memset(xb, 0, sizeof(*xb));
361
362 return buf;
363}
364
382static inline void agxbuf_trim_zeros(agxbuf *xb) {
383
384 // find last period
385 char *start = agxbstart(xb);
386 size_t period;
387 for (period = agxblen(xb) - 1;; --period) {
388 if (period == SIZE_MAX) {
389 // we searched the entire string and did not find a period
390 return;
391 }
392 if (start[period] == '.') {
393 break;
394 }
395 }
396
397 // truncate any “0”s that provide no information
398 for (size_t follower = agxblen(xb) - 1;; --follower) {
399 if (follower == period || start[follower] == '0') {
400 // truncate this character
401 if (agxbuf_is_inline(xb)) {
402 assert(xb->located > AGXBUF_INLINE_SIZE_0);
403 --xb->located;
404 } else {
405 --xb->size;
406 }
407 if (follower == period) {
408 break;
409 }
410 } else {
411 return;
412 }
413 }
414
415 // is the remainder we have left not “-0”?
416 const size_t len = agxblen(xb);
417 if (len < 2 || start[len - 2] != '-' || start[len - 1] != '0') {
418 return;
419 }
420
421 // turn “-0” into “0”
422 start[len - 2] = '0';
423 if (agxbuf_is_inline(xb)) {
424 assert(xb->located > AGXBUF_INLINE_SIZE_0);
425 --xb->located;
426 } else {
427 --xb->size;
428 }
429}
static int agxbpop(agxbuf *xb)
removes last character added, if any
Definition agxbuf.h:130
static size_t agxbsizeof(const agxbuf *xb)
Definition agxbuf.h:122
static void agxbuf_trim_zeros(agxbuf *xb)
Definition agxbuf.h:382
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static size_t agxbput(agxbuf *xb, const char *s)
append string s into xb
Definition agxbuf.h:288
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:268
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static char * agxbnext(agxbuf *xb)
next position for writing
Definition agxbuf.h:175
static void agxbclear(agxbuf *xb)
resets pointer to data
Definition agxbuf.h:312
static int vagxbprint(agxbuf *xb, const char *fmt, va_list ap)
vprintf-style output to an agxbuf
Definition agxbuf.h:181
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
static void agxbmore(agxbuf *xb, size_t ssz)
expand buffer to hold at least ssz more bytes
Definition agxbuf.h:150
static size_t agxblen(const agxbuf *xb)
return number of characters currently stored
Definition agxbuf.h:108
static char * agxbstart(agxbuf *xb)
return pointer to beginning of buffer
Definition agxbuf.h:103
static bool agxbuf_is_inline(const agxbuf *xb)
Definition agxbuf.h:90
#define PRINTF_LIKE(index, first)
Definition agxbuf.h:248
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:295
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:345
agxbuf_loc_t
a description of where a buffer is located
Definition agxbuf.h:49
@ AGXBUF_ON_HEAP
Definition agxbuf.h:51
@ AGXBUF_INLINE_SIZE_0
Definition agxbuf.h:50
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:85
char * buf
start of buffer
Definition agxbuf.h:77
size_t size
number of characters in the buffer
Definition agxbuf.h:78
unsigned char located
where does the backing memory for this buffer live?
Definition agxbuf.h:82
size_t capacity
available bytes in the buffer
Definition agxbuf.h:79
Definition grammar.c:90
abstraction for squashing compiler warnings for unused symbols
#define WUR
Definition unused.h:43