agxbuf.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /**
  2. * @file
  3. * @ingroup cgraph_utils
  4. */
  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. #pragma once
  15. #include <assert.h>
  16. #include <limits.h>
  17. #include <stdarg.h>
  18. #include <stdbool.h>
  19. #include <stddef.h>
  20. #include <stdint.h>
  21. #include <stdio.h>
  22. #include <string.h>
  23. #include <util/alloc.h>
  24. /// a description of where a buffer is located
  25. typedef enum {
  26. AGXBUF_INLINE_SIZE_0 = 0,
  27. AGXBUF_ON_HEAP = 255, ///< buffer is dynamically allocated
  28. /// other values mean an inline buffer with size N
  29. } agxbuf_loc_t;
  30. /// extensible buffer
  31. ///
  32. /// Malloc'ed memory is never released until \p agxbdisown or \p agxbfree is
  33. /// called.
  34. ///
  35. /// This has the following layout assuming x86-64.
  36. ///
  37. /// located
  38. /// ↓
  39. /// ┌───────────────┬───────────────┬───────────────┬─────────────┬─┐
  40. /// │ buf │ size │ capacity │ padding │ │
  41. /// ├───────────────┴───────────────┴───────────────┴─────────────┼─┤
  42. /// │ store │ │
  43. /// └─────────────────────────────────────────────────────────────┴─┘
  44. /// 0 8 16 24 32
  45. ///
  46. /// \p buf, \p size, and \p capacity are in use when \p located is
  47. /// \p AGXBUF_ON_HEAP. \p store is in use when \p located is <
  48. /// \p AGXBUF_ON_HEAP.
  49. typedef struct {
  50. union {
  51. struct {
  52. char *buf; ///< start of buffer
  53. size_t size; ///< number of characters in the buffer
  54. size_t capacity; ///< available bytes in the buffer
  55. char padding[sizeof(size_t) - 1]; ///< unused; for alignment
  56. unsigned char
  57. located; ///< where does the backing memory for this buffer live?
  58. } s;
  59. char store[sizeof(char *) + sizeof(size_t) * 3 -
  60. 1]; ///< inline storage used when \p located is
  61. ///< < \p AGXBUF_ON_HEAP
  62. } u;
  63. } agxbuf;
  64. static inline bool agxbuf_is_inline(const agxbuf *xb) {
  65. assert((xb->u.s.located == AGXBUF_ON_HEAP ||
  66. xb->u.s.located <= sizeof(xb->u.store)) &&
  67. "corrupted agxbuf type");
  68. return xb->u.s.located < AGXBUF_ON_HEAP;
  69. }
  70. /// free any malloced resources
  71. static inline void agxbfree(agxbuf *xb) {
  72. if (xb->u.s.located == AGXBUF_ON_HEAP)
  73. free(xb->u.s.buf);
  74. }
  75. /// return pointer to beginning of buffer
  76. static inline char *agxbstart(agxbuf *xb) {
  77. return agxbuf_is_inline(xb) ? xb->u.store : xb->u.s.buf;
  78. }
  79. /// return number of characters currently stored
  80. static inline size_t agxblen(const agxbuf *xb) {
  81. if (agxbuf_is_inline(xb)) {
  82. return xb->u.s.located - AGXBUF_INLINE_SIZE_0;
  83. }
  84. return xb->u.s.size;
  85. }
  86. /// get the capacity of the backing memory of a buffer
  87. ///
  88. /// In contrast to \p agxblen, this is the total number of usable bytes in the
  89. /// backing store, not the total number of currently stored bytes.
  90. ///
  91. /// \param xb Buffer to operate on
  92. /// \return Number of usable bytes in the backing store
  93. static inline size_t agxbsizeof(const agxbuf *xb) {
  94. if (agxbuf_is_inline(xb)) {
  95. return sizeof(xb->u.store);
  96. }
  97. return xb->u.s.capacity;
  98. }
  99. /// removes last character added, if any
  100. static inline int agxbpop(agxbuf *xb) {
  101. size_t len = agxblen(xb);
  102. if (len == 0) {
  103. return -1;
  104. }
  105. if (agxbuf_is_inline(xb)) {
  106. assert(xb->u.s.located > AGXBUF_INLINE_SIZE_0);
  107. int c = xb->u.store[len - 1];
  108. --xb->u.s.located;
  109. return c;
  110. }
  111. int c = xb->u.s.buf[xb->u.s.size - 1];
  112. --xb->u.s.size;
  113. return c;
  114. }
  115. /// expand buffer to hold at least ssz more bytes
  116. static inline void agxbmore(agxbuf *xb, size_t ssz) {
  117. size_t cnt = 0; // current no. of characters in buffer
  118. size_t size = 0; // current buffer size
  119. size_t nsize = 0; // new buffer size
  120. char *nbuf; // new buffer
  121. size = agxbsizeof(xb);
  122. nsize = size == 0 ? BUFSIZ : (2 * size);
  123. if (size + ssz > nsize)
  124. nsize = size + ssz;
  125. cnt = agxblen(xb);
  126. if (xb->u.s.located == AGXBUF_ON_HEAP) {
  127. nbuf = (char *)gv_recalloc(xb->u.s.buf, size, nsize, sizeof(char));
  128. } else {
  129. nbuf = (char *)gv_calloc(nsize, sizeof(char));
  130. memcpy(nbuf, xb->u.store, cnt);
  131. xb->u.s.size = cnt;
  132. }
  133. xb->u.s.buf = nbuf;
  134. xb->u.s.capacity = nsize;
  135. xb->u.s.located = AGXBUF_ON_HEAP;
  136. }
  137. /// next position for writing
  138. static inline char *agxbnext(agxbuf *xb) {
  139. size_t len = agxblen(xb);
  140. return agxbuf_is_inline(xb) ? &xb->u.store[len] : &xb->u.s.buf[len];
  141. }
  142. /// vprintf-style output to an agxbuf
  143. static inline int vagxbprint(agxbuf *xb, const char *fmt, va_list ap) {
  144. size_t size;
  145. int result;
  146. // determine how many bytes we need to print
  147. {
  148. va_list ap2;
  149. int rc;
  150. va_copy(ap2, ap);
  151. rc = vsnprintf(NULL, 0, fmt, ap2);
  152. va_end(ap2);
  153. if (rc < 0) {
  154. va_end(ap);
  155. return rc;
  156. }
  157. size = (size_t)rc + 1; // account for NUL terminator
  158. }
  159. // should we use double buffering?
  160. bool use_stage = false;
  161. // do we need to expand the buffer?
  162. {
  163. size_t unused_space = agxbsizeof(xb) - agxblen(xb);
  164. if (unused_space < size) {
  165. size_t extra = size - unused_space;
  166. if (agxbuf_is_inline(xb) && extra == 1) {
  167. // The content is currently stored inline, but this print will push it
  168. // over into being heap-allocated by a single byte. This last byte is a
  169. // '\0' that `vsnprintf` unavoidably writes but we do not need. So lets
  170. // avoid this by printing to an intermediate, larger buffer, and then
  171. // copying the content minus the trailing '\0' to the final destination.
  172. use_stage = true;
  173. } else {
  174. agxbmore(xb, extra);
  175. }
  176. }
  177. }
  178. // a buffer one byte larger than inline storage to fit the trailing '\0'
  179. char stage[sizeof(xb->u.store) + 1] = {0};
  180. assert(!use_stage || size <= sizeof(stage));
  181. // we can now safely print into the buffer
  182. char *dst = use_stage ? stage : agxbnext(xb);
  183. result = vsnprintf(dst, size, fmt, ap);
  184. assert(result == (int)(size - 1) || result < 0);
  185. if (result > 0) {
  186. if (agxbuf_is_inline(xb)) {
  187. assert(result <= (int)UCHAR_MAX);
  188. if (use_stage) {
  189. memcpy(agxbnext(xb), stage, (size_t)result);
  190. }
  191. xb->u.s.located += (unsigned char)result;
  192. assert(agxblen(xb) <= sizeof(xb->u.store) && "agxbuf corruption");
  193. } else {
  194. assert(!use_stage);
  195. xb->u.s.size += (size_t)result;
  196. }
  197. }
  198. return result;
  199. }
  200. /* support for extra API misuse warnings if available */
  201. #ifdef __GNUC__
  202. #define PRINTF_LIKE(index, first) __attribute__((format(printf, index, first)))
  203. #else
  204. #define PRINTF_LIKE(index, first) /* nothing */
  205. #endif
  206. /// Printf-style output to an agxbuf
  207. static inline PRINTF_LIKE(2, 3) int agxbprint(agxbuf *xb, const char *fmt,
  208. ...) {
  209. va_list ap;
  210. int result;
  211. va_start(ap, fmt);
  212. result = vagxbprint(xb, fmt, ap);
  213. va_end(ap);
  214. return result;
  215. }
  216. #undef PRINTF_LIKE
  217. /// append string s of length ssz into xb
  218. static inline size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz) {
  219. if (ssz == 0) {
  220. return 0;
  221. }
  222. if (ssz > agxbsizeof(xb) - agxblen(xb))
  223. agxbmore(xb, ssz);
  224. size_t len = agxblen(xb);
  225. if (agxbuf_is_inline(xb)) {
  226. memcpy(&xb->u.store[len], s, ssz);
  227. assert(ssz <= UCHAR_MAX);
  228. xb->u.s.located += (unsigned char)ssz;
  229. assert(agxblen(xb) <= sizeof(xb->u.store) && "agxbuf corruption");
  230. } else {
  231. memcpy(&xb->u.s.buf[len], s, ssz);
  232. xb->u.s.size += ssz;
  233. }
  234. return ssz;
  235. }
  236. /// append string s into xb
  237. static inline size_t agxbput(agxbuf *xb, const char *s) {
  238. size_t ssz = strlen(s);
  239. return agxbput_n(xb, s, ssz);
  240. }
  241. /// add character to buffer
  242. static inline int agxbputc(agxbuf *xb, char c) {
  243. if (agxblen(xb) >= agxbsizeof(xb)) {
  244. agxbmore(xb, 1);
  245. }
  246. size_t len = agxblen(xb);
  247. if (agxbuf_is_inline(xb)) {
  248. xb->u.store[len] = c;
  249. ++xb->u.s.located;
  250. assert(agxblen(xb) <= sizeof(xb->u.store) && "agxbuf corruption");
  251. } else {
  252. xb->u.s.buf[len] = c;
  253. ++xb->u.s.size;
  254. }
  255. return 0;
  256. }
  257. /// resets pointer to data
  258. static inline void agxbclear(agxbuf *xb) {
  259. if (agxbuf_is_inline(xb)) {
  260. xb->u.s.located = AGXBUF_INLINE_SIZE_0;
  261. } else {
  262. xb->u.s.size = 0;
  263. }
  264. }
  265. /* Null-terminates buffer; resets and returns pointer to data. The buffer is
  266. * still associated with the agxbuf and will be overwritten on the next, e.g.,
  267. * agxbput. If you want to retrieve and disassociate the buffer, use agxbdisown
  268. * instead.
  269. */
  270. static inline char *agxbuse(agxbuf *xb) {
  271. if (!agxbuf_is_inline(xb) || agxblen(xb) != sizeof(xb->u.store)) {
  272. (void)agxbputc(xb, '\0');
  273. } else {
  274. // we can skip explicitly null-terminating the buffer because `agxbclear`
  275. // resets the `xb->located` byte such that it naturally forms a terminator
  276. assert(AGXBUF_INLINE_SIZE_0 == '\0');
  277. }
  278. agxbclear(xb);
  279. return agxbstart(xb);
  280. }
  281. /* Disassociate the backing buffer from this agxbuf and return it. The buffer is
  282. * NUL terminated before being returned. If the agxbuf is using stack memory,
  283. * this will first copy the data to a new heap buffer to then return. If you
  284. * want to temporarily access the string in the buffer, but have it overwritten
  285. * and reused the next time, e.g., agxbput is called, use agxbuse instead of
  286. * agxbdisown.
  287. */
  288. static inline char *agxbdisown(agxbuf *xb) {
  289. char *buf;
  290. if (agxbuf_is_inline(xb)) {
  291. // the string lives in `store`, so we need to copy its contents to heap
  292. // memory
  293. buf = gv_strndup(xb->u.store, agxblen(xb));
  294. } else {
  295. // the buffer is already dynamically allocated, so terminate it and then
  296. // take it as-is
  297. agxbputc(xb, '\0');
  298. buf = xb->u.s.buf;
  299. }
  300. // reset xb to a state where it is usable
  301. memset(xb, 0, sizeof(*xb));
  302. return buf;
  303. }
  304. /** trim extraneous trailing information from a printed floating point value
  305. *
  306. * tl;dr:
  307. * - “42.00” → “42”
  308. * - “42.01” → “42.01”
  309. * - “42.10” → “42.1”
  310. * - “-0.0” → “0”
  311. *
  312. * Printing a \p double or \p float via, for example,
  313. * \p agxbprint("%.02f", 42.003) can result in output like “42.00”. If this data
  314. * is destined for something that does generalized floating point
  315. * parsing/decoding (e.g. SVG viewers) the “.00” is unnecessary. “42” would be
  316. * interpreted identically. This function can be called after such a
  317. * \p agxbprint to normalize data.
  318. *
  319. * \param xb Buffer to operate on
  320. */
  321. static inline void agxbuf_trim_zeros(agxbuf *xb) {
  322. // find last period
  323. char *start = agxbstart(xb);
  324. size_t period;
  325. for (period = agxblen(xb) - 1;; --period) {
  326. if (period == SIZE_MAX) {
  327. // we searched the entire string and did not find a period
  328. return;
  329. }
  330. if (start[period] == '.') {
  331. break;
  332. }
  333. }
  334. // truncate any “0”s that provide no information
  335. for (size_t follower = agxblen(xb) - 1;; --follower) {
  336. if (follower == period || start[follower] == '0') {
  337. // truncate this character
  338. if (agxbuf_is_inline(xb)) {
  339. assert(xb->u.s.located > AGXBUF_INLINE_SIZE_0);
  340. --xb->u.s.located;
  341. } else {
  342. --xb->u.s.size;
  343. }
  344. if (follower == period) {
  345. break;
  346. }
  347. } else {
  348. return;
  349. }
  350. }
  351. // is the remainder we have left not “-0”?
  352. const size_t len = agxblen(xb);
  353. if (len < 2 || start[len - 2] != '-' || start[len - 1] != '0') {
  354. return;
  355. }
  356. // turn “-0” into “0”
  357. start[len - 2] = '0';
  358. if (agxbuf_is_inline(xb)) {
  359. assert(xb->u.s.located > AGXBUF_INLINE_SIZE_0);
  360. --xb->u.s.located;
  361. } else {
  362. --xb->u.s.size;
  363. }
  364. }