123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- /**
- * @file
- * @ingroup cgraph_utils
- */
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #pragma once
- #include <assert.h>
- #include <limits.h>
- #include <stdarg.h>
- #include <stdbool.h>
- #include <stddef.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <string.h>
- #include <util/alloc.h>
- /// a description of where a buffer is located
- typedef enum {
- AGXBUF_INLINE_SIZE_0 = 0,
- AGXBUF_ON_HEAP = 255, ///< buffer is dynamically allocated
- /// other values mean an inline buffer with size N
- } agxbuf_loc_t;
- /// extensible buffer
- ///
- /// Malloc'ed memory is never released until \p agxbdisown or \p agxbfree is
- /// called.
- ///
- /// This has the following layout assuming x86-64.
- ///
- /// located
- /// ↓
- /// ┌───────────────┬───────────────┬───────────────┬─────────────┬─┐
- /// │ buf │ size │ capacity │ padding │ │
- /// ├───────────────┴───────────────┴───────────────┴─────────────┼─┤
- /// │ store │ │
- /// └─────────────────────────────────────────────────────────────┴─┘
- /// 0 8 16 24 32
- ///
- /// \p buf, \p size, and \p capacity are in use when \p located is
- /// \p AGXBUF_ON_HEAP. \p store is in use when \p located is <
- /// \p AGXBUF_ON_HEAP.
- typedef struct {
- union {
- struct {
- char *buf; ///< start of buffer
- size_t size; ///< number of characters in the buffer
- size_t capacity; ///< available bytes in the buffer
- char padding[sizeof(size_t) - 1]; ///< unused; for alignment
- unsigned char
- located; ///< where does the backing memory for this buffer live?
- } s;
- char store[sizeof(char *) + sizeof(size_t) * 3 -
- 1]; ///< inline storage used when \p located is
- ///< < \p AGXBUF_ON_HEAP
- } u;
- } agxbuf;
- static inline bool agxbuf_is_inline(const agxbuf *xb) {
- assert((xb->u.s.located == AGXBUF_ON_HEAP ||
- xb->u.s.located <= sizeof(xb->u.store)) &&
- "corrupted agxbuf type");
- return xb->u.s.located < AGXBUF_ON_HEAP;
- }
- /// free any malloced resources
- static inline void agxbfree(agxbuf *xb) {
- if (xb->u.s.located == AGXBUF_ON_HEAP)
- free(xb->u.s.buf);
- }
- /// return pointer to beginning of buffer
- static inline char *agxbstart(agxbuf *xb) {
- return agxbuf_is_inline(xb) ? xb->u.store : xb->u.s.buf;
- }
- /// return number of characters currently stored
- static inline size_t agxblen(const agxbuf *xb) {
- if (agxbuf_is_inline(xb)) {
- return xb->u.s.located - AGXBUF_INLINE_SIZE_0;
- }
- return xb->u.s.size;
- }
- /// get the capacity of the backing memory of a buffer
- ///
- /// In contrast to \p agxblen, this is the total number of usable bytes in the
- /// backing store, not the total number of currently stored bytes.
- ///
- /// \param xb Buffer to operate on
- /// \return Number of usable bytes in the backing store
- static inline size_t agxbsizeof(const agxbuf *xb) {
- if (agxbuf_is_inline(xb)) {
- return sizeof(xb->u.store);
- }
- return xb->u.s.capacity;
- }
- /// removes last character added, if any
- static inline int agxbpop(agxbuf *xb) {
- size_t len = agxblen(xb);
- if (len == 0) {
- return -1;
- }
- if (agxbuf_is_inline(xb)) {
- assert(xb->u.s.located > AGXBUF_INLINE_SIZE_0);
- int c = xb->u.store[len - 1];
- --xb->u.s.located;
- return c;
- }
- int c = xb->u.s.buf[xb->u.s.size - 1];
- --xb->u.s.size;
- return c;
- }
- /// expand buffer to hold at least ssz more bytes
- static inline void agxbmore(agxbuf *xb, size_t ssz) {
- size_t cnt = 0; // current no. of characters in buffer
- size_t size = 0; // current buffer size
- size_t nsize = 0; // new buffer size
- char *nbuf; // new buffer
- size = agxbsizeof(xb);
- nsize = size == 0 ? BUFSIZ : (2 * size);
- if (size + ssz > nsize)
- nsize = size + ssz;
- cnt = agxblen(xb);
- if (xb->u.s.located == AGXBUF_ON_HEAP) {
- nbuf = (char *)gv_recalloc(xb->u.s.buf, size, nsize, sizeof(char));
- } else {
- nbuf = (char *)gv_calloc(nsize, sizeof(char));
- memcpy(nbuf, xb->u.store, cnt);
- xb->u.s.size = cnt;
- }
- xb->u.s.buf = nbuf;
- xb->u.s.capacity = nsize;
- xb->u.s.located = AGXBUF_ON_HEAP;
- }
- /// next position for writing
- static inline char *agxbnext(agxbuf *xb) {
- size_t len = agxblen(xb);
- return agxbuf_is_inline(xb) ? &xb->u.store[len] : &xb->u.s.buf[len];
- }
- /// vprintf-style output to an agxbuf
- static inline int vagxbprint(agxbuf *xb, const char *fmt, va_list ap) {
- size_t size;
- int result;
- // determine how many bytes we need to print
- {
- va_list ap2;
- int rc;
- va_copy(ap2, ap);
- rc = vsnprintf(NULL, 0, fmt, ap2);
- va_end(ap2);
- if (rc < 0) {
- va_end(ap);
- return rc;
- }
- size = (size_t)rc + 1; // account for NUL terminator
- }
- // should we use double buffering?
- bool use_stage = false;
- // do we need to expand the buffer?
- {
- size_t unused_space = agxbsizeof(xb) - agxblen(xb);
- if (unused_space < size) {
- size_t extra = size - unused_space;
- if (agxbuf_is_inline(xb) && extra == 1) {
- // The content is currently stored inline, but this print will push it
- // over into being heap-allocated by a single byte. This last byte is a
- // '\0' that `vsnprintf` unavoidably writes but we do not need. So lets
- // avoid this by printing to an intermediate, larger buffer, and then
- // copying the content minus the trailing '\0' to the final destination.
- use_stage = true;
- } else {
- agxbmore(xb, extra);
- }
- }
- }
- // a buffer one byte larger than inline storage to fit the trailing '\0'
- char stage[sizeof(xb->u.store) + 1] = {0};
- assert(!use_stage || size <= sizeof(stage));
- // we can now safely print into the buffer
- char *dst = use_stage ? stage : agxbnext(xb);
- result = vsnprintf(dst, size, fmt, ap);
- assert(result == (int)(size - 1) || result < 0);
- if (result > 0) {
- if (agxbuf_is_inline(xb)) {
- assert(result <= (int)UCHAR_MAX);
- if (use_stage) {
- memcpy(agxbnext(xb), stage, (size_t)result);
- }
- xb->u.s.located += (unsigned char)result;
- assert(agxblen(xb) <= sizeof(xb->u.store) && "agxbuf corruption");
- } else {
- assert(!use_stage);
- xb->u.s.size += (size_t)result;
- }
- }
- return result;
- }
- /* support for extra API misuse warnings if available */
- #ifdef __GNUC__
- #define PRINTF_LIKE(index, first) __attribute__((format(printf, index, first)))
- #else
- #define PRINTF_LIKE(index, first) /* nothing */
- #endif
- /// Printf-style output to an agxbuf
- static inline PRINTF_LIKE(2, 3) int agxbprint(agxbuf *xb, const char *fmt,
- ...) {
- va_list ap;
- int result;
- va_start(ap, fmt);
- result = vagxbprint(xb, fmt, ap);
- va_end(ap);
- return result;
- }
- #undef PRINTF_LIKE
- /// append string s of length ssz into xb
- static inline size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz) {
- if (ssz == 0) {
- return 0;
- }
- if (ssz > agxbsizeof(xb) - agxblen(xb))
- agxbmore(xb, ssz);
- size_t len = agxblen(xb);
- if (agxbuf_is_inline(xb)) {
- memcpy(&xb->u.store[len], s, ssz);
- assert(ssz <= UCHAR_MAX);
- xb->u.s.located += (unsigned char)ssz;
- assert(agxblen(xb) <= sizeof(xb->u.store) && "agxbuf corruption");
- } else {
- memcpy(&xb->u.s.buf[len], s, ssz);
- xb->u.s.size += ssz;
- }
- return ssz;
- }
- /// append string s into xb
- static inline size_t agxbput(agxbuf *xb, const char *s) {
- size_t ssz = strlen(s);
- return agxbput_n(xb, s, ssz);
- }
- /// add character to buffer
- static inline int agxbputc(agxbuf *xb, char c) {
- if (agxblen(xb) >= agxbsizeof(xb)) {
- agxbmore(xb, 1);
- }
- size_t len = agxblen(xb);
- if (agxbuf_is_inline(xb)) {
- xb->u.store[len] = c;
- ++xb->u.s.located;
- assert(agxblen(xb) <= sizeof(xb->u.store) && "agxbuf corruption");
- } else {
- xb->u.s.buf[len] = c;
- ++xb->u.s.size;
- }
- return 0;
- }
- /// resets pointer to data
- static inline void agxbclear(agxbuf *xb) {
- if (agxbuf_is_inline(xb)) {
- xb->u.s.located = AGXBUF_INLINE_SIZE_0;
- } else {
- xb->u.s.size = 0;
- }
- }
- /* Null-terminates buffer; resets and returns pointer to data. The buffer is
- * still associated with the agxbuf and will be overwritten on the next, e.g.,
- * agxbput. If you want to retrieve and disassociate the buffer, use agxbdisown
- * instead.
- */
- static inline char *agxbuse(agxbuf *xb) {
- if (!agxbuf_is_inline(xb) || agxblen(xb) != sizeof(xb->u.store)) {
- (void)agxbputc(xb, '\0');
- } else {
- // we can skip explicitly null-terminating the buffer because `agxbclear`
- // resets the `xb->located` byte such that it naturally forms a terminator
- assert(AGXBUF_INLINE_SIZE_0 == '\0');
- }
- agxbclear(xb);
- return agxbstart(xb);
- }
- /* Disassociate the backing buffer from this agxbuf and return it. The buffer is
- * NUL terminated before being returned. If the agxbuf is using stack memory,
- * this will first copy the data to a new heap buffer to then return. If you
- * want to temporarily access the string in the buffer, but have it overwritten
- * and reused the next time, e.g., agxbput is called, use agxbuse instead of
- * agxbdisown.
- */
- static inline char *agxbdisown(agxbuf *xb) {
- char *buf;
- if (agxbuf_is_inline(xb)) {
- // the string lives in `store`, so we need to copy its contents to heap
- // memory
- buf = gv_strndup(xb->u.store, agxblen(xb));
- } else {
- // the buffer is already dynamically allocated, so terminate it and then
- // take it as-is
- agxbputc(xb, '\0');
- buf = xb->u.s.buf;
- }
- // reset xb to a state where it is usable
- memset(xb, 0, sizeof(*xb));
- return buf;
- }
- /** trim extraneous trailing information from a printed floating point value
- *
- * tl;dr:
- * - “42.00” → “42”
- * - “42.01” → “42.01”
- * - “42.10” → “42.1”
- * - “-0.0” → “0”
- *
- * Printing a \p double or \p float via, for example,
- * \p agxbprint("%.02f", 42.003) can result in output like “42.00”. If this data
- * is destined for something that does generalized floating point
- * parsing/decoding (e.g. SVG viewers) the “.00” is unnecessary. “42” would be
- * interpreted identically. This function can be called after such a
- * \p agxbprint to normalize data.
- *
- * \param xb Buffer to operate on
- */
- static inline void agxbuf_trim_zeros(agxbuf *xb) {
- // find last period
- char *start = agxbstart(xb);
- size_t period;
- for (period = agxblen(xb) - 1;; --period) {
- if (period == SIZE_MAX) {
- // we searched the entire string and did not find a period
- return;
- }
- if (start[period] == '.') {
- break;
- }
- }
- // truncate any “0”s that provide no information
- for (size_t follower = agxblen(xb) - 1;; --follower) {
- if (follower == period || start[follower] == '0') {
- // truncate this character
- if (agxbuf_is_inline(xb)) {
- assert(xb->u.s.located > AGXBUF_INLINE_SIZE_0);
- --xb->u.s.located;
- } else {
- --xb->u.s.size;
- }
- if (follower == period) {
- break;
- }
- } else {
- return;
- }
- }
- // is the remainder we have left not “-0”?
- const size_t len = agxblen(xb);
- if (len < 2 || start[len - 2] != '-' || start[len - 1] != '0') {
- return;
- }
- // turn “-0” into “0”
- start[len - 2] = '0';
- if (agxbuf_is_inline(xb)) {
- assert(xb->u.s.located > AGXBUF_INLINE_SIZE_0);
- --xb->u.s.located;
- } else {
- --xb->u.s.size;
- }
- }
|