Graphviz 14.0.3~dev.20251028.0232
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 return rc;
174 }
175 size = (size_t)rc + 1; // account for NUL terminator
176 }
177
178 // should we use double buffering?
179 bool use_stage = false;
180
181 // do we need to expand the buffer?
182 {
183 size_t unused_space = agxbsizeof(xb) - agxblen(xb);
184 if (unused_space < size) {
185 size_t extra = size - unused_space;
186 if (agxbuf_is_inline(xb) && extra == 1) {
187 // The content is currently stored inline, but this print will push it
188 // over into being heap-allocated by a single byte. This last byte is a
189 // '\0' that `vsnprintf` unavoidably writes but we do not need. So lets
190 // avoid this by printing to an intermediate, larger buffer, and then
191 // copying the content minus the trailing '\0' to the final destination.
192 use_stage = true;
193 } else {
194 agxbmore(xb, extra);
195 }
196 }
197 }
198
199 // a buffer one byte larger than inline storage to fit the trailing '\0'
200 char stage[sizeof(xb->store) + 1] = {0};
201 assert(!use_stage || size <= sizeof(stage));
202
203 // we can now safely print into the buffer
204 char *dst = use_stage ? stage : agxbnext(xb);
205 result = vsnprintf(dst, size, fmt, ap);
206 assert(result == (int)(size - 1) || result < 0);
207 if (result > 0) {
208 if (agxbuf_is_inline(xb)) {
209 assert(result <= (int)UCHAR_MAX);
210 if (use_stage) {
211 memcpy(agxbnext(xb), stage, (size_t)result);
212 }
213 xb->located += (unsigned char)result;
214 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
215 } else {
216 assert(!use_stage);
217 xb->size += (size_t)result;
218 }
219 }
220
221 return result;
222}
223
224/* support for extra API misuse warnings if available */
225#ifdef __GNUC__
226#define PRINTF_LIKE(index, first) __attribute__((format(printf, index, first)))
227#else
228#define PRINTF_LIKE(index, first) /* nothing */
229#endif
230
232static inline PRINTF_LIKE(2, 3) int agxbprint(agxbuf *xb, const char *fmt,
233 ...) {
234 va_list ap;
235 int result;
236
237 va_start(ap, fmt);
238
239 result = vagxbprint(xb, fmt, ap);
240
241 va_end(ap);
242 return result;
243}
244
245#undef PRINTF_LIKE
246
248static inline size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz) {
249 if (ssz == 0) {
250 return 0;
251 }
252 if (ssz > agxbsizeof(xb) - agxblen(xb))
253 agxbmore(xb, ssz);
254 size_t len = agxblen(xb);
255 if (agxbuf_is_inline(xb)) {
256 memcpy(&xb->store[len], s, ssz);
257 assert(ssz <= UCHAR_MAX);
258 xb->located += (unsigned char)ssz;
259 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
260 } else {
261 memcpy(&xb->buf[len], s, ssz);
262 xb->size += ssz;
263 }
264 return ssz;
265}
266
268static inline size_t agxbput(agxbuf *xb, const char *s) {
269 size_t ssz = strlen(s);
270
271 return agxbput_n(xb, s, ssz);
272}
273
275static inline int agxbputc(agxbuf *xb, char c) {
276 if (agxblen(xb) >= agxbsizeof(xb)) {
277 agxbmore(xb, 1);
278 }
279 size_t len = agxblen(xb);
280 if (agxbuf_is_inline(xb)) {
281 xb->store[len] = c;
282 ++xb->located;
283 assert(agxblen(xb) <= sizeof(xb->store) && "agxbuf corruption");
284 } else {
285 xb->buf[len] = c;
286 ++xb->size;
287 }
288 return 0;
289}
290
292static inline void agxbclear(agxbuf *xb) {
293 if (agxbuf_is_inline(xb)) {
295 } else {
296 xb->size = 0;
297 }
298}
299
300/* Null-terminates buffer; resets and returns pointer to data. The buffer is
301 * still associated with the agxbuf and will be overwritten on the next, e.g.,
302 * agxbput. If you want to retrieve and disassociate the buffer, use agxbdisown
303 * instead.
304 */
305static inline WUR char *agxbuse(agxbuf *xb) {
306 if (!agxbuf_is_inline(xb) || agxblen(xb) != sizeof(xb->store)) {
307 (void)agxbputc(xb, '\0');
308 } else {
309 // we can skip explicitly null-terminating the buffer because `agxbclear`
310 // resets the `xb->located` byte such that it naturally forms a terminator
311 assert(AGXBUF_INLINE_SIZE_0 == '\0');
312 }
313
314 agxbclear(xb);
315 return agxbstart(xb);
316}
317
318/* Disassociate the backing buffer from this agxbuf and return it. The buffer is
319 * NUL terminated before being returned. If the agxbuf is using stack memory,
320 * this will first copy the data to a new heap buffer to then return. If you
321 * want to temporarily access the string in the buffer, but have it overwritten
322 * and reused the next time, e.g., agxbput is called, use agxbuse instead of
323 * agxbdisown.
324 */
325static inline char *agxbdisown(agxbuf *xb) {
326 char *buf;
327
328 if (agxbuf_is_inline(xb)) {
329 // the string lives in `store`, so we need to copy its contents to heap
330 // memory
331 buf = gv_strndup(xb->store, agxblen(xb));
332 } else {
333 // the buffer is already dynamically allocated, so terminate it and then
334 // take it as-is
335 agxbputc(xb, '\0');
336 buf = xb->buf;
337 }
338
339 // reset xb to a state where it is usable
340 memset(xb, 0, sizeof(*xb));
341
342 return buf;
343}
344
362static inline void agxbuf_trim_zeros(agxbuf *xb) {
363
364 // find last period
365 char *start = agxbstart(xb);
366 size_t period;
367 for (period = agxblen(xb) - 1;; --period) {
368 if (period == SIZE_MAX) {
369 // we searched the entire string and did not find a period
370 return;
371 }
372 if (start[period] == '.') {
373 break;
374 }
375 }
376
377 // truncate any “0”s that provide no information
378 for (size_t follower = agxblen(xb) - 1;; --follower) {
379 if (follower == period || start[follower] == '0') {
380 // truncate this character
381 if (agxbuf_is_inline(xb)) {
382 assert(xb->located > AGXBUF_INLINE_SIZE_0);
383 --xb->located;
384 } else {
385 --xb->size;
386 }
387 if (follower == period) {
388 break;
389 }
390 } else {
391 return;
392 }
393 }
394
395 // is the remainder we have left not “-0”?
396 const size_t len = agxblen(xb);
397 if (len < 2 || start[len - 2] != '-' || start[len - 1] != '0') {
398 return;
399 }
400
401 // turn “-0” into “0”
402 start[len - 2] = '0';
403 if (agxbuf_is_inline(xb)) {
404 assert(xb->located > AGXBUF_INLINE_SIZE_0);
405 --xb->located;
406 } else {
407 --xb->size;
408 }
409}
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:362
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:268
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:248
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:232
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:292
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:305
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:228
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:275
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:325
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