+
+/* Support macros. These assume `lyt' is defined as a pointer to the `struct
+ * layout' state.
+ */
+
+#define SPLIT_RANGE(tail, base, limit) do { \
+ /* Set TAIL to point just after the last nonspace character between \
+ * BASE and LIMIT. If there are no nonspace characters, then set \
+ * TAIL to equal BASE. \
+ */ \
+ \
+ for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
+} while (0)
+
+#define PUT_RANGE(base, limit) do { \
+ /* Write the range of characters between BASE and LIMIT to the output \
+ * file. Return immediately on error. \
+ */ \
+ \
+ size_t _n = limit - base; \
+ if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
+} while (0)
+
+#define PUT_CHAR(ch) do { \
+ /* Write CH to the output. Return immediately on error. */ \
+ \
+ if (putc(ch, lyt->fp) == EOF) return (-1); \
+} while (0)
+
+#define PUT_PREFIX do { \
+ /* Output the prefix, if there is one. Return immediately on error. */ \
+ \
+ if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
+} while (0)
+
+#define PUT_SAVED do { \
+ /* Output the saved trailing blank material in the buffer. */ \
+ \
+ size_t _n = lyt->w.len; \
+ if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
+} while (0)
+
+#define PUT_PFXINB do { \
+ /* Output the initial nonblank portion of the prefix, if there is \
+ * one. Return immediately on error. \
+ */ \
+ \
+ if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
+} while (0)
+
+#define SAVE_PFXTAIL do { \
+ /* Save the trailing blank portion of the prefix. */ \
+ \
+ if (lyt->prefix) \
+ DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
+} while (0)
+
+/* --- @set_layout_prefix@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state
+ * @const char *prefix@ = new prefix string or null
+ *
+ * Returns: ---
+ *
+ * Use: Change the configured prefix string. The change takes effect
+ * at the start of the next line (or the current line if it's
+ * empty or only whitespace so far).
+ */
+
+static void set_layout_prefix(struct layout *lyt, const char *prefix)
+{
+ const char *q, *l;
+
+ if (!prefix || !*prefix)
+ lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
+ else {
+ lyt->prefix = prefix;
+ l = lyt->pfxlim = prefix + strlen(prefix);
+ SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
+ }
+}
+
+/* --- @init_layout@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state to initialize
+ * @FILE *fp@ = output file
+ * @const char *prefix@ = prefix string (or null if empty)
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a layout state.
+ */
+
+static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
+{
+ lyt->fp = fp;
+ lyt->f = LYTF_NEWL;
+ dstr_create(&lyt->w);
+ set_layout_prefix(lyt, prefix);
+}
+
+/* --- @destroy_layout@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state
+ * @unsigned f@ = flags (@DLF_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Releases a layout state and the resources it holds.
+ * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
+ * it open (in case it's @stderr@ or something).
+ */
+
+#define DLF_CLOSE 1u
+static void destroy_layout(struct layout *lyt, unsigned f)
+{
+ if (f&DLF_CLOSE) fclose(lyt->fp);
+ dstr_destroy(&lyt->w);
+}
+
+/* --- @layout_char@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state
+ * @int ch@ = character to write
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Write a single character to the output.
+ */
+
+static int layout_char(struct layout *lyt, int ch)
+{
+ if (ch == '\n') {
+ if (lyt->f&LYTF_NEWL) PUT_PFXINB;
+ PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
+ } else if (isspace(ch))
+ DPUTC(&lyt->w, ch);
+ else {
+ if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
+ PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
+ }
+ return (0);
+}
+
+/* --- @layout_string@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state
+ * @const char *p@ = string to write
+ * @size_t sz@ = length of string
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Write a string to the output.
+ */
+
+static int layout_string(struct layout *lyt, const char *p, size_t sz)
+{
+ const char *q, *r, *l = p + sz;
+
+ /* This is rather vexing. There are a small number of jobs to do, but the
+ * logic for deciding which to do when gets rather hairy if, as I've tried
+ * here, one aims to minimize the number of decisions being checked, so
+ * it's worth canning them into macros.
+ *
+ * Here, a `blank' is a whitespace character other than newline. The input
+ * buffer consists of one or more `segments', each of which consists of:
+ *
+ * * an initial portion, which is either empty or ends with a nonblank
+ * character;
+ *
+ * * a suffix which consists only of blanks; and
+ *
+ * * an optional newline.
+ *
+ * All segments except the last end with a newline.
+ */
+
+#define SPLIT_SEGMENT do { \
+ /* Determine the bounds of the current segment. If there is a final \
+ * newline, then q is non-null and points to this newline; otherwise, \
+ * q is null. The initial portion of the segment lies between p .. r \
+ * and the blank suffix lies between r .. q (or r .. l if q is null). \
+ * This sounds awkward, but the suffix is only relevant if there is \
+ * no newline. \
+ */ \
+ \
+ q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
+} while (0)
+
+#define PUT_NONBLANK do { \
+ /* Output the initial portion of the segment. */ \
+ \
+ PUT_RANGE(p, r); \
+} while (0)
+
+#define PUT_NEWLINE do { \
+ /* Write a newline, and advance to the next segment. */ \
+ \
+ PUT_CHAR('\n'); p = q + 1; \
+} while (0)
+
+#define SAVE_TAIL do { \
+ /* Save the trailing blank portion of the segment in the buffer. \
+ * Assumes that there is no newline, since otherwise the suffix would \
+ * be omitted. \
+ */ \
+ \
+ DPUTM(&lyt->w, r, l - r); \
+} while (0)
+
+ /* Determine the bounds of the first segment. Handling this is the most
+ * complicated part of this function.
+ */
+ SPLIT_SEGMENT;
+
+ if (!q) {
+ /* This is the only segment. We'll handle the whole thing here.
+ *
+ * If there's an initial nonblank portion, then we need to write that
+ * out. Furthermore, if we're at the start of the line then we'll need
+ * to write the prefix, and if there's saved blank material then we'll
+ * need to write that. Otherwise, there's only blank stuff, which we
+ * accumulate in the buffer.
+ *
+ * If we're at the start of a line here, then put the prefix followed by
+ * any saved whitespace, and then our initial nonblank portion. Then
+ * save our new trailing space.
+ */
+
+ if (r > p) {
+ if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
+ PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
+ }
+ SAVE_TAIL;
+ return (0);
+ }
+
+ /* There is at least one more segment, so we know that there'll be a line
+ * to output.
+ */
+ if (r > p) {
+ if (lyt->f&LYTF_NEWL) PUT_PREFIX;
+ PUT_SAVED; PUT_NONBLANK;
+ } else if (lyt->f&LYTF_NEWL)
+ PUT_PFXINB;
+ PUT_NEWLINE; DRESET(&lyt->w);
+ SPLIT_SEGMENT;
+
+ /* Main loop over whole segments with trailing newlines. For each one, we
+ * know that we're starting at the beginning of a line and there's a final
+ * newline, so we write the initial prefix and drop the trailing blanks.
+ */
+ while (q) {
+ if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
+ else PUT_PFXINB;
+ PUT_NEWLINE;
+ SPLIT_SEGMENT;
+ }
+
+ /* At the end, there's no final newline. If there's nonblank material,
+ * then we can write the prefix and the nonblank stuff. Otherwise, stash
+ * the blank stuff (including the trailing blanks of the prefix) and leave
+ * the newline flag set.
+ */
+ if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
+ else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
+ SAVE_TAIL;
+
+#undef SPLIT_SEGMENT
+#undef PUT_NONBLANK
+#undef PUT_NEWLINE
+#undef SAVE_TAIL
+
+ return (0);
+}
+
+#undef SPLIT_RANGE
+#undef PUT_RANGE
+#undef PUT_PREFIX
+#undef PUT_PFXINB
+#undef PUT_SAVED
+#undef PUT_CHAR
+#undef SAVE_PFXTAIL
+