chiark / gitweb /
@@@ man wip
authorMark Wooding <mdw@distorted.org.uk>
Sat, 9 Mar 2024 23:20:43 +0000 (23:20 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 9 Mar 2024 23:20:57 +0000 (23:20 +0000)
51 files changed:
buf/lbuf.3
buf/pkbuf.3
codec/base64.3
codec/codec.3
codec/url.3
hash/t/hash-test.c
hash/unihash.3
mem/Makefile.am
mem/arena.3
mem/growbuf.3 [new file with mode: 0644]
mem/growbuf.h [new file with mode: 0644]
mem/pool.3
sel/bres.3
sel/conn.3
sel/ident.3
sel/sel.3
sel/selbuf.3
sel/selpk.3
sel/sig.3
struct/buf-putf.c
struct/buf.3
struct/buf.c
struct/buf.h
struct/darray.3
struct/darray.c
struct/dstr-putf.c
struct/dstr.c
struct/hash.3
struct/sym.3
sys/fdflags.3
sys/lock.3
sys/mdup.3
test/t/tvec-test.c
test/testrig.3
test/tests.at
test/tvec-core.c
test/tvec-output.c
test/tvec-remote.c
test/tvec-types.c
test/tvec.3
test/tvec.h
trace/trace.3
ui/mdwopt.3
utils/Makefile.am
utils/control.3
utils/gprintf.3 [new file with mode: 0644]
utils/gprintf.c
utils/gprintf.h
utils/linreg.3 [new file with mode: 0644]
utils/linreg.h
utils/maths.3 [new file with mode: 0644]

index d1663a2ec26b642afb4e2711b29992ad64a4df7d..0842cb8325a65812004d84aacc8e92b18ddcc46b 100644 (file)
@@ -11,18 +11,19 @@ lbuf \- split lines out of asynchronously received blocks
 .\" @lbuf_destroy
 .SH "SYNOPSIS"
 .nf
+.ta 2n
 .B "#include <mLib/lbuf.h>"
 
 .B "enum {"
-.B "\h'4n'LBUF_CRLF,"
-.B "\h'4n'LBUF_STRICTCRLF,"
-.B "\h'4n'..."
+.B "   LBUF_CRLF,"
+.B "   LBUF_STRICTCRLF,"
+.B "   ..."
 .B "};"
 .B "#define LBUF_ENABLE ..."
 
 .B "typedef struct {"
-.B "\h'4n'unsigned f;"
-.B "\h'4n'..."
+.B "   unsigned f;"
+.B "   ..."
 .B "} lbuf;"
 
 .B "typedef void lbuf_func(char *" s ", size_t " len ", void *" p );
index 01c42404adbd3730045903d4f2bb9458cd3db717..50a30fd709eb7449b0c26dfa8c2e6544c21cec00 100644 (file)
@@ -11,20 +11,21 @@ pkbuf \- split packets out of asynchronously received blocks
 .\" @pkbuf_destroy
 .SH "SYNOPSIS"
 .nf
+.ta 2n
 .B "#include <mLib/pkbuf.h>"
 
 .B "enum {"
-.B "\h'4n'PKBUF_ENABLE = ..."
+.B "   PKBUF_ENABLE = ..."
 .B "};"
 
 .B "typedef struct {"
-.B "\h'4n'unsigned f;"
-.B "\h'4n'..."
+.B "   unsigned f;"
+.B "   ..."
 .B "} pkbuf;"
 
-.ds mT \fBtypedef void pkbuf_func(
-.B "\*(mToctet *" b ", size_t " sz ", pkbuf *" p ,
-.BI "\h'\w'\*(mT'u'size_t *" keep ", void *" p );
+.ta \w'\fBtypedef void pkbuf_func('u
+.B "typedef void pkbuf_func(octet *" b ", size_t " sz ", pkbuf *" p ,
+.BI "  size_t *" keep ", void *" p );
 
 .BI "void pkbuf_flush(pkbuf *" pk ", octet *" p ", size_t " len );
 .BI "void pkbuf_close(pkbuf *" pk );
index 39de50d5c0a516c04e37451f89a4742007a0b18c..9c44d12207fba6dc1d19fedcef62c30c5e5e9f76 100644 (file)
@@ -17,46 +17,52 @@ base64, base32, hex \- obsolete binary encoding functions
 .B "#include <mLib/base32.h>"
 .B "#include <mLib/hex.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'char *indent;"
-.B "\h'4n'unsigned maxline;"
-.B "\h'4n'..."
+.B "   char *indent;"
+.B "   unsigned maxline;"
+.B "   ..."
 .B "} base64_ctx;"
 
+.ta \w'\fBvoid base64_encode('u
 .BI "void base64_encode(base64_ctx *" ctx ,
-.BI "                   const void *" p ", size_t " sz ,
-.BI "                   dstr *" d );
+.BI "  const void *" p ", size_t " sz ,
+.BI "  dstr *" d );
 .BI "void base64_decode(base64_ctx *" ctx ,
-.BI "                   const void *" p ", size_t " sz ,
-.BI "                   dstr *" d );
+.BI "  const void *" p ", size_t " sz ,
+.BI "  dstr *" d );
 .BI "void base64_init(base64_ctx *" ctx );
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'char *indent;"
-.B "\h'4n'unsigned maxline;"
-.B "\h'4n'..."
+.B "   char *indent;"
+.B "   unsigned maxline;"
+.B "   ..."
 .B "} base32_ctx;"
 
+.ta \w'\fBvoid base32_encode('u
 .BI "void base32_encode(base32_ctx *" ctx ,
-.BI "                   const void *" p ", size_t " sz ,
-.BI "                   dstr *" d );
+.BI "  const void *" p ", size_t " sz ,
+.BI "  dstr *" d );
 .BI "void base32_decode(base32_ctx *" ctx ,
-.BI "                   const void *" p ", size_t " sz ,
-.BI "                   dstr *" d );
+.BI "  const void *" p ", size_t " sz ,
+.BI "  dstr *" d );
 .BI "void base32_init(base32_ctx *" ctx );
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'char *indent;"
-.B "\h'4n'unsigned maxline;"
-.B "\h'4n'..."
+.B "   char *indent;"
+.B "   unsigned maxline;"
+.B "   ..."
 .B "} hex_ctx;"
 
+.ta \w'\fBvoid hex_encode('u
 .BI "void hex_encode(hex_ctx *" ctx ,
-.BI "                const void *" p ", size_t " sz ,
-.BI "                dstr *" d );
+.BI "  const void *" p ", size_t " sz ,
+.BI "  dstr *" d );
 .BI "void hex_decode(hex_ctx *" ctx ,
-.BI "                const void *" p ", size_t " sz ,
-.BI "                dstr *" d );
+.BI "  const void *" p ", size_t " sz ,
+.BI "  dstr *" d );
 .BI "void hex_init(hex_ctx *" ctx );
 .fi
 .SH DESCRIPTION
index ee5a447651b83549e6b50c088dd4f0df425d1cef..6f2ccd82477c24106fd03ae17aaace186babb2a0 100644 (file)
@@ -29,30 +29,31 @@ codec \- binary encoding and decoding
 .B "#define CDCF_IGNSPC ..."
 .B "#define CDCF_IGNJUNK ..."
 
+.ta 2n
 .B "enum {"
-.B "\h'4n'CDCERR_OK = ...,"
-.B "\h'4n'CDCERR_INVCH = ...,"
-.B "\h'4n'CDCERR_INVEQPAD = ...,"
-.B "\h'4n'CDCERR_INVZPAD = ..."
+.B "   CDCERR_OK = ...,"
+.B "   CDCERR_INVCH = ...,"
+.B "   CDCERR_INVEQPAD = ...,"
+.B "   CDCERR_INVZPAD = ..."
 .B "};"
 
 .B "typedef struct {"
-.B "\h'4n'const char *name;"
-.ds mT \fBcodec *(*encoder)(
-.BI "\h'4n'\*(mTunsigned " flags ,
-.BI "\h'4n+\w'\*(mT'u'const char *" indent ", unsigned " maxlen );
-.BI "\h'4n'codec *(*decoder)(unsigned " flags );
-.B "\h'4n'...\&"
+.B "   const char *name;"
+.ta 2n +\w'\fBcodec *(*encoder)('u
+.BI "  codec *(*encoder)(unsigned " flags ,
+.BI "          const char *" indent ", unsigned " maxlen );
+.BI "  codec *(*decoder)(unsigned " flags );
+.B "   ...\&"
 .B "} codec_class;"
 
 .B "typedef struct {"
-.B "\h'4n'const codec_ops *ops;"
+.B "   const codec_ops *ops;"
 .B "} codec;"
 
 .B "typedef struct {"
-.B "\h'4n'const codec_class *c;"
-.BI "\h'4n'int (*code)(codec *" c ", const void *" p ", size_t " sz ", dstr *" d );
-.BI "\h'4n'void (*destroy)(codec *" c );
+.B "   const codec_class *c;"
+.BI "  int (*code)(codec *" c ", const void *" p ", size_t " sz ", dstr *" d );
+.BI "  void (*destroy)(codec *" c );
 .B "} codec_ops;"
 
 .B "codec_class null_codec_class;"
index c3b4e980fd79ea33643571fd48ba7a4eba35f3ca..9e8e8804cdf085eb4b2c078eef64dfb0ad504b06 100644 (file)
@@ -22,14 +22,15 @@ url \- manipulation of form-urlencoded strings
 .nf
 .B "#include <mLib/url.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'unsigned f;"
-.B "\h'4n'..."
+.B "   unsigned f;"
+.B "   ..."
 .B "} url_ectx;"
 
 .B "typedef struct {"
-.B "\h'4n'unsigned f;"
-.B "\h'4n'..."
+.B "   unsigned f;"
+.B "   ..."
 .B "} url_dctx;"
 
 .B "#define URLF_STRICT ..."
@@ -37,9 +38,9 @@ url \- manipulation of form-urlencoded strings
 .B "#define URLF_SEMI ..."
 
 .BI "void url_initenc(url_ectx *" ctx );
-.ds mT \fBvoid url_enc(
-.BI "\*(mTurl_ectx *" ctx ", dstr *" d ,
-.BI "\h'\w'\*(mT'u'const char *" name ", const char *" value );
+.ta \w'\fBvoid url_enc('u
+.BI "void url_enc(url_ectx *" ctx ", dstr *" d ,
+.BI "  const char *" name ", const char *" value );
 
 .BI "void url_initdec(url_dctx *" ctx ", const char *" p );
 .BI "int url_dec(url_dctx *" ctx ", dstr *" n ", dstr *" v );
@@ -143,6 +144,7 @@ its use.)
 The example code below demonstrates converting between a symbol table
 and a urlencoded representation.  The code is untested.
 .VS
+.ta 2n +2n
 #include <stdlib.h>
 #include <mLib/alloc.h>
 #include <mLib/dstr.h>
@@ -150,37 +152,36 @@ and a urlencoded representation.  The code is untested.
 #include <mLib/url.h>
 
 typedef struct {
-  sym_base _b;
-  char *v;
+       sym_base _b;
+       char *v;
 } val;
 
 void decode(sym_table *t, const char *p)
 {
-  url_dctx c;
-  dstr n = DSTR_INIT, v = DSTR_INIT;
+       url_dctx c;
+       dstr n = DSTR_INIT, v = DSTR_INIT;
+       val *vv;
+       unsigned f;
 
-  for (url_initdec(&c, p); url_dec(&c, &n, &v); ) {
-    unsigned f;
-    val *vv = sym_find(t, n.buf, -1, sizeof(*vv), &f);
-    if (f)
-      free(vv->v);
-    vv->v = xstrdup(v.buf);
-    DRESET(&n);
-    DRESET(&v);
-  }
-  dstr_destroy(&n);
-  dstr_destroy(&v);
+       for (url_initdec(&c, p); url_dec(&c, &n, &v); ) {
+               vv = sym_find(t, n.buf, -1, sizeof(*vv), &f);
+               if (f) free(vv->v);
+               vv->v = xstrdup(v.buf);
+               DRESET(&n);
+               DRESET(&v);
+       }
+       dstr_destroy(&n); dstr_destroy(&v);
 }
 
 void encode(sym_table *t, dstr *d)
 {
-  sym_iter i;
-  url_ectx c;
-  val *v;
+       sym_iter i;
+       url_ectx c;
+       val *v;
 
-  url_initenc(&c);
-  for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; )
-    url_enc(&c, d, SYM_NAME(v), v->v);
+       url_initenc(&c);
+       for (sym_mkiter(&i, t); (v = sym_next(&i)) != 0; )
+               url_enc(&c, d, SYM_NAME(v), v->v);
 }
 .VE
 .SH "SEE ALSO"
index 1bb21e9766435c648dba6e53751adb4974ad9950..3c637dc9ce9eb05c59bc591369392cd365292f64 100644 (file)
@@ -56,9 +56,14 @@ static void test_crc32(const struct tvec_reg *in, struct tvec_reg *out,
   }
 }
 
+static void before_hash(struct tvec_state *tv, void *ctx)
+  { tvec_allocbuffer(&tv->in[RM].v); }
+
 static void bench_crc32(const struct tvec_reg *in, struct tvec_reg *out,
                        void *ctx)
   { crc32(0, in[RM].v.bytes.p, in[RM].v.bytes.sz); }
+static const struct tvec_env crc32_benchenv =
+  { 0, 0, 0, before_hash };
 
 static void test_unihash(const struct tvec_reg *in, struct tvec_reg *out,
                         void *ctx)
@@ -83,9 +88,9 @@ static void bench_unihash(const struct tvec_reg *in, struct tvec_reg *out,
   { unihash_hash(ctx, 0, in[RM].v.bytes.p, in[RM].v.bytes.sz); }
 static void setup_unihash(struct tvec_state *tv,
                          const struct tvec_env *env, void *pctx, void *ctx)
-  { unihash_setkey(ctx, 0); }
+  { tvec_allocbuffer(&tv->in[RM].v); unihash_setkey(ctx, 0); }
 static const struct tvec_env unihash_benchenv =
-  { sizeof(unihash_info), setup_unihash, 0, 0 };
+  { sizeof(unihash_info), setup_unihash, 0, before_hash };
 
 static void run_step(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 {
@@ -124,7 +129,7 @@ static const struct tvec_regdef bench_regs[] = {
 };
 
 static const struct tvec_benchenv crc32_bench =
-  { TVEC_BENCHINIT, 1, -1, RM, 0 };
+  { TVEC_BENCHINIT, 1, -1, RM, &crc32_benchenv };
 static const struct tvec_benchenv unihash_bench =
   { TVEC_BENCHINIT, 1, -1, RM, &unihash_benchenv };
 
index c6b4c5865dda1024cf57cddecffd12d2613c839a..72467acf909b3762ff03338a529cca49511cd2b6 100644 (file)
@@ -51,8 +51,9 @@ unihash \- simple and efficient universal hashing for hashtables
 
 .BI "void unihash_setkey(unihash_info *" i ", uint32 " k );
 .BI "uint32 UNIHASH_INIT(const unihash_info *" i );
+.ta \w'\fBuint32 unihash_hash('u
 .BI "uint32 unihash_hash(const unihash_info *" i ", uint32 " a ,
-.BI "                    const void *" p ", size_t " sz );
+.BI "  const void *" p ", size_t " sz );
 .BI "uint32 unihash(const unihash_info *" i ", const void *" p ", size_t " sz );
 .BI "uint32 UNIHASH(const unihash_info *" i ", const void *" p ", size_t " sz );
 .fi
@@ -119,7 +120,7 @@ the data so far. So, to hash multiple chunks of data, do something like
 uint32 a = UNIHASH_INIT(i);
 a = unihash_hash(i, a, p_0, sz_0);
 a = unihash_hash(i, a, p_1, sz_1);
-/* ... */
+/* ...\& */
 a = unihash_hash(i, a, p_n, sz_n);
 .VE
 The macro
index 7beb771d13b847f86f8ce13818ad986429e255d2..595fba396854d85752f4f6c9bae16d214319a089 100644 (file)
@@ -42,6 +42,10 @@ pkginclude_HEADERS   += alloc.h
 libmem_la_SOURCES      += alloc.c
 LIBMANS                        += alloc.3
 
+## Buffer extension.
+pkginclude_HEADERS     += growbuf.h
+LIBMANS                        += growbuf.3
+
 ## Slab allocator.
 pkginclude_HEADERS     += sub.h
 libmem_la_SOURCES      += sub.c
index 3baf4e6d7ed0eafe605d0a949fc8904071ec2133..51c2227dc5d7217c626c8847ff1f5b150bf50035 100644 (file)
@@ -15,22 +15,24 @@ arena \- control of memory allocation
 .nf
 .B "#include <mLib/arena.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'const struct arena_ops *ops";
+.B "   const struct arena_ops *ops";
 .B "} arena;"
 
 .B "typedef struct {"
-.BI "\h'4n'void *(*alloc)(arena *" a ", size_t " sz );
-.BI "\h'4n'void *(*realloc)(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
-.BI "\h'4n'void *(*free)(arena *" a ", void *" p );
-.BI "\h'4n'void *(*purge)(arena *" a );
+.BI "  void *(*alloc)(arena *" a ", size_t " sz );
+.BI "  void *(*realloc)(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
+.BI "  void *(*free)(arena *" a ", void *" p );
+.BI "  void *(*purge)(arena *" a );
 .B "} arena_ops;"
 
 .BI "arena *arena_global;"
 .BI "arena arena_stdlib;"
 
+.ta \w'\fBvoid *arena_fakerealloc('u
 .BI "void *arena_fakerealloc(arena *" a ", void *" p ,
-.BI "                        size_t " sz ", size_t " osz );
+.BI "  size_t " sz ", size_t " osz );
 
 .BI "void *a_alloc(arena *" a ", size_t " sz );
 .BI "void *a_realloc(arena *" a ", void *" p ", size_t " sz ", size_t " osz );
diff --git a/mem/growbuf.3 b/mem/growbuf.3
new file mode 100644 (file)
index 0000000..c31f27c
--- /dev/null
@@ -0,0 +1,98 @@
+.\" -*-nroff-*-
+.TH growbuf 3 "9 March 2024" "Straylight/Edgeware" "mLib utilities library"
+.\" @GROWBUF_SIZE
+.\" @GROWBUF_EXTEND
+.\" @GROWBUF_REPLACE
+.
+.SH NAME
+growbuf \- extend buffers efficiently
+.
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/growbuf.h>"
+
+.BI "GROWBUF_SIZE(size_t " sz ", size_t " want ", " \c
+.BI "size_t " init ", size_t " granule ");"
+
+.ds mT \fBGROWBUF_EXTEND(
+.BI "\*(mTarena *" a ", " type " *" buf ", size_t " sz ", size_t " want ","
+.BI "\h'\w'\*(mT'u'size_t " init ", size_t " granule ");"
+.ds mT \fBGROWBUF_REPLACE(
+.BI "\*(mTarena *" a ", " type " *" buf ", size_t " sz ", size_t " want ","
+.BI "\h'\w'\*(mT'u'size_t " init ", size_t " granule ");"
+.fi
+.
+.SH DESCRIPTION
+The
+.B "<mLib/growbuf.h>"
+header file defines macros useful for dynamically resizing buffers.
+.PP
+The situation envisaged is a buffer, at address
+.IR buf ,
+with space to hold
+.I sz
+objects each of size
+.IR granule ;
+but it has become necessary to store
+.I want
+objects in the buffer.
+If the current size
+.I sz
+is zero, then the buffer pointer
+.I buf
+may be null;
+otherwise, it is assumed to have been allocated from the arena
+.IR a .
+The size
+.I init
+is a suggested minimal nonzero size, again counting objects of size
+.IR granule .
+.PP
+The
+.B GROWBUF_SIZE
+macro merely adjusts
+.I sz
+so that it is at least as large as
+.IR want ;
+it is
+.I assumed
+on entry that
+.IR sz "\ <\ " want ;
+nothing especially terrible will happen if this is not the case,
+but unnecessary work will take place,
+and
+.I sz
+will be left nonzero if it was previously zero,
+even if
+.IR want "\ =\ 0."
+.B GROWBUF_SIZE
+arranges to increase buffer sizes in a geometric progression
+so as to minimize reallocation and copying overheads \(en
+the number of reallocations will be
+at most logarithmic in the final size
+and each byte is copied an (amortized) constant number of times.
+.PP
+The
+.B GROWBUF_EXTEND
+macro will additionally resize the buffer,
+preserving its contents,
+and adjusting
+.I buf
+to point to its new location;
+if
+.I want
+\(<=
+.I sz
+then nothing happens.
+The
+.B GROWBUF_REPLACE
+macro is similar, except that it
+.I discards
+the existing buffer contents if the buffer needs to be adjusted.
+.
+.SH "SEE ALSO"
+.BR arena (3),
+.BR mLib (3).
+.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/mem/growbuf.h b/mem/growbuf.h
new file mode 100644 (file)
index 0000000..ec78bcd
--- /dev/null
@@ -0,0 +1,137 @@
+/* -*-c-*-
+ *
+ * Grow a buffer if it's too small
+ *
+ * (c) 2024 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef MLIB_GROWBUF_H
+#define MLIB_GROWBUF_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <stddef.h>
+
+#ifndef MLIB_ALLOC_H
+#  include "alloc.h"
+#endif
+
+/*----- Macros provided ---------------------------------------------------*/
+
+/* --- @GROWBUF_LIMIT@ --- *
+ *
+ * Arguments:  @size_t granule@ = allocation granule
+ *
+ * Returns:    The largest number %$n$% such that the total size of %$n$%
+ *             objects, each of size @granule@, can be represented in a
+ *             @size_t@.
+ */
+
+#define GROWBUF_LIMIT(granule) (~(size_t)0/(granule))
+
+/* --- @GROWBUF_SIZE@ --- *
+ *
+ * Arguments:  @size_t sz@ = the current size (updated)
+ *             @size_t want@ = the desired minimum size
+ *             @size_t init@ = a suitable initial size
+ *             @size_t granule@ = the allocation granule size
+ *
+ * Returns:    ---
+ *
+ * Use:                On entry, @sz@ should be the current capacity of some buffer,
+ *             in terms of objects of size @granule@, and @want@ a needed
+ *             capacity, in the same terms, with @want > sz@; @init@ should
+ *             be some suitable positive initial size, in case the current
+ *             size is zero.  The macro updates @sz@ to be some suitable new
+ *             positive size at least as large as @want@.
+ */
+
+#define GROWBUF_SIZE(sz, want, init, granule) do {                     \
+  size_t _sz_ = (sz), _want_ = (want);                                 \
+                                                                       \
+  assert(_want_ < GROWBUF_LIMIT(granule)/2);                           \
+  if (!_sz_) _sz_ = (init);                                            \
+  while (_sz_ < _want_) _sz_ *= 2;                                     \
+  (sz) = _sz_;                                                         \
+} while (0)
+
+/* --- @GROWBUF_EXTEND@, @GROWBUF_REPLACE@ --- *
+ *
+ * Arguments:  @arena *a@ = pointer to an arena
+ *             @type *buf@ = pointer to some buffer, possibly null (updated)
+ *             @size_t sz@ = current buffer size (updated)
+ *             @size_t want@ = desired minimum size
+ *             @size_t init@ = a suitable initial size
+ *             @size_t granule@ = the allocation granule size
+ *
+ * Returns:    ---
+ *
+ * Use:                On entry, @buf@ should be a pointer to a buffer, allocated
+ *             from the arena @a@, with space for @sz@ objects of size
+ *             @granule@; @buf@ may be null if @sz@ is zero.  On exit, @buf@
+ *             and @sz@ will be updated to refer to a possibly different
+ *             buffer, with space for at least @want@ objects (but certainly
+ *             not smaller than before).
+ *
+ *             @GROWBUF_EXTEND@ preserves the contents of the buffer;
+ *             @GROWBUF_REPLACE@ discards the existing contents.
+ */
+
+#define GROWBUF_EXTEND(a, buf, sz, want, init, granule) do {           \
+  size_t _sz0 = (sz), _sz = _sz0, _want = (want), _gr = (granule);     \
+  void *_p = (buf);                                                    \
+  arena *_a = (a);                                                     \
+                                                                       \
+  if (_sz < _want) {                                                   \
+    GROWBUF_SIZE(_sz, _want, init, _gr);                               \
+    if (!_p) _p = x_alloc(_a, _sz*_gr);                                        \
+    else _p = x_realloc(_a, _p, _sz*_gr, _sz0*_gr);                    \
+    (buf) = _p; (sz) = _sz;                                            \
+  }                                                                    \
+} while (0)
+
+#define GROWBUF_REPLACE(a, buf, sz, want, init, granule) do {          \
+  size_t _sz0 = (sz), _sz = _sz0, _want = (want), _gr = (granule);     \
+  void *_p = (buf);                                                    \
+  arena *_a = (a);                                                     \
+                                                                       \
+  if (_sz < _want) {                                                   \
+    GROWBUF_SIZE(_sz, want, init, _gr);                                        \
+    if (_p) x_free(_a, _p);                                            \
+    _p = x_alloc(_a, _sz*_gr);                                         \
+    (buf) = _p; (sz) = _sz;                                            \
+  }                                                                    \
+} while (0)
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
index 5acc7b27beac772beb2a68d91737035b0910bed9..38538a8204634fc3ccee68f20871f5f72dc47e47 100644 (file)
@@ -31,30 +31,33 @@ pool \- resource pool management
 
 .B "typedef struct { ...\& } pool;"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'pool_resource *next;"
-.BI "\h'4n'void (*destroy)(pool_resource *" r );
+.B "   pool_resource *next;"
+.BI "  void (*destroy)(pool_resource *" r );
 .B "} pool_resource;"
 
 .B "typedef struct {"
-.B "\h'4n'FILE *fp;"
-.B "\h'4n'..."
+.B "   FILE *fp;"
+.B "   ..."
 .B "} pool_file;"
 
 .BI "void pool_init(pool *" p ", arena *" a );
 .BI "pool *pool_create(arena *" a );
 .BI "pool *pool_sub(pool *" p );
 .BI "void pool_destroy(pool *" p );
+.ta \w'\fBvoid pool_add('u
 .BI "void pool_add(pool *" p ", pool_resource *" r ,
-.BI "              void (*" dfn ")(pool_resource *" r ));
+.BI "  void (*" dfn ")(pool_resource *" r ));
 .BI "void *pool_alloc(pool *" p ", size_t " sz );
 .BI "char *pool_strdup(pool *" p ", const char *" s );
 .BI "pool_file *pool_fopen(pool *" p ", const char *" file ", const char *" how );
 .BI "int pool_fclose(pool_file *" pf );
 .BI "subarena *pool_subarena(pool *" p );
 
+.ta \w'\fBvoid POOL_ADD('u
 .BI "void POOL_ADD(pool *" p ", pool_resource *" r ,
-.BI "              void (*" dfn ")(pool_resource *" r ));
+.BI "  void (*" dfn ")(pool_resource *" r ));
 .fi
 .SH "DESCRIPTION"
 .SS "Overview"
index a18e24eb859a295ff5d42c9663017eb9b0d912bb..b5539b09d7b2e2ce88648035335effcf8fbf5086 100644 (file)
@@ -13,14 +13,14 @@ bres \- background name resolver
 
 .B "typedef struct { ...\& } bres_client;"
 
-.ds mT \fBvoid bres_byname(
-.BI "\*(mTbres_client *" rc ", const char *" name ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(struct hostent *" h ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
-.ds mT \fBvoid bres_byaddr(
-.BI "\*(mTbres_client *" rc ", struct inaddr " addr ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(struct hostent *" h ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBvoid bres_byname('u
+.BI "void bres_byname(bres_client *" rc ", const char *" name ,
+.BI "  void (*" func ")(struct hostent *" h ", void *" p ),
+.BI "  void *" p );
+.ta \w'\fBvoid bres_byaddr('u
+.BI "void bres_byaddr(res_client *" rc ", struct inaddr " addr ,
+.BI "  void (*" func ")(struct hostent *" h ", void *" p ),
+.BI "  void *" p );
 .BI "void bres_abort(bres_client *" rc );
 .BI "void bres_exec(const char *" file );
 .BI "void bres_init(sel_state *" sel );
@@ -50,10 +50,11 @@ Each function is passed the following arguments:
 Pointer to the client block to initialize and store the resolver job's
 state.
 .TP
-.BI "struct in_addr " addr "\fR (\fBbres_byaddr\fR)"
-.sp -1
+.BR "struct in_addr " \fIaddr ""  " (" bres_byaddr )
+.ie t .sp -0.4v
+.el .sp -1v
 .TP
-.BI "const char *" name "\fR (\fBbres_byname\fR)"
+.BR "const char *" \fIname ""  " (" bres_byname )
 The IP address or hostname to resolve.
 .TP
 .BI "void (*" func ")(struct hostent *" h ", void *" p )
index f115605cee5d5814375b481aa94a3e1461617444..1ff936fcc6684b9271926694cf5224d6b978c569 100644 (file)
@@ -11,16 +11,16 @@ conn \- selector for nonblocking connections
 
 .B "typedef struct { ...\& } conn;"
 
-.ds mT \fBint conn_fd(
-.BI "\*(mTconn *" c ", sel_state *" s ", int " fd ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(int " fd ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBint conn_fd('u
+.BI "int conn_fd(conn *" c ", sel_state *" s ", int " fd ,
+.BI "  void (*" func ")(int " fd ", void *" p ),
+.BI "  void *" p );
 
-.ds mT \fBint conn_init(
-.BI "\*(mTconn *" c ", sel_state *" s ", int " fd ,
-.BI "\h'\w'\*(mT'u'struct sockaddr *" dst ", int " dsz ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(int " fd ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBint conn_init('u
+.BI "int conn_init(conn *" c ", sel_state *" s ", int " fd ,
+.BI "  struct sockaddr *" dst ", int " dsz ,
+.BI "  void (*" func ")(int " fd ", void *" p ),
+.BI "  void *" p );
 
 .BI "void conn_kill(conn *" c );
 .fi
index 326d50892e4f634f2aef9cc2d0cba1da313e57e0..aebf9034f282efdf802011eac1bfffd29176f5f2 100644 (file)
@@ -11,32 +11,33 @@ ident \- identd (RFC931) client
 
 .B "typedef struct { ...\& } ident_request;"
 
+.ta 2n +2n
 .B "enum ["
-.B "\h'4n'IDENT_USERID = ...,"
-.B "\h'4n'IDENT_ERROR = ...,"
-.B "\h'4n'IDENT_BAD = ..."
+.B "   IDENT_USERID = ...,"
+.B "   IDENT_ERROR = ...,"
+.B "   IDENT_BAD = ..."
 .B "};"
 
 .B "typedef struct {"
-.B "\h'4n'unsigned short sport, dport;"
-.B "\h'4n'unsigned type;"
-.B "\h'4n'union {"
-.B "\h'8n'struct { char *os, *user; } userid;"
-.B "\h'8n'char *error;"
-.B "\h'4n'} u;"
+.B "   unsigned short sport, dport;"
+.B "   unsigned type;"
+.B "   union {"
+.B "           struct { char *os, *user; } userid;"
+.B "           char *error;"
+.B "   } u;"
 .B "} ident_reply;"
 
 .BI "void ident_abort(ident_request *" rq );
-.ds mT \fBvoid ident(
-.BI "\*(mTident_request *" rq ", sel_state *" s ,
-.BI "\h'\w'\*(mT'u'const struct sockaddr_in *" local ,
-.BI "\h'\w'\*(mT'u'const struct sockaddr_in *" remote ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(ident_reply *" i ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
-.ds mT \fBvoid ident_socket(
-.BI "\*(mTident_request *" rq ", sel_state *" s ", int " sk ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(ident_reply *" i ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBvoid ident('u
+.BI "void ident(ident_request *" rq ", sel_state *" s ,
+.BI "  const struct sockaddr_in *" local ,
+.BI "  const struct sockaddr_in *" remote ,
+.BI "  void (*" func ")(ident_reply *" i ", void *" p ),
+.BI "  void *" p );
+.ta \w'\fBvoid ident_socket('u
+.BI "void ident_socket(ident_request *" rq ", sel_state *" s ", int " sk ,
+.BI "  void (*" func ")(ident_reply *" i ", void *" p ),
+.BI "  void *" p );
 .fi
 .SH "DESCRIPTION"
 The
index 021b1c977ad14d7ee453a96ef4a47e5a32bf1677..e31227727c2eeedbf3bd38fb8c75dc9a7584543e 100644 (file)
--- a/sel/sel.3
+++ b/sel/sel.3
@@ -17,11 +17,12 @@ sel \- low level interface for waiting for I/O
 .nf
 .B "#include <mLib/sel.h>"
 
+.ta 2n
 .B "enum {"
-.B "\h'4n'SEL_READ = ...,"
-.B "\h'4n'SEL_WRITE = ...,"
-.B "\h'4n'SEL_EXC = ...,"
-.B "\h'4n'SEL_MODES = ..."
+.B "   SEL_READ = ...,"
+.B "   SEL_WRITE = ...,"
+.B "   SEL_EXC = ...,"
+.B "   SEL_MODES = ..."
 .B "};"
 
 .B "typedef struct { ...\& } sel_state;"
@@ -29,41 +30,41 @@ sel \- low level interface for waiting for I/O
 .B "typedef struct { ...\& } sel_hook;"
 
 .B "typedef struct {"
-.B "\h'4n'int fd;"
-.B "\h'4n'..."
+.B "   int fd;"
+.B "   ..."
 .B "} sel_file;"
 
 .B "typedef struct {"
-.B "\h'4n'int maxfd;"
-.B "\h'4n'fd_set fd[SEL_MODES];"
-.B "\h'4n'struct timeval tv, *tvp;"
-.B "\h'4n'struct timeval now;"
+.B "   int maxfd;"
+.B "   fd_set fd[SEL_MODES];"
+.B "   struct timeval tv, *tvp;"
+.B "   struct timeval now;"
 .B "} sel_args;"
 
 .BI "typedef void (*sel_hookfn)(sel_state *" s ", sel_args *" a ", void *" p );
 
 .BI "void sel_init(sel_state *" s );
 
-.ds mT \fBvoid sel_initfile(
-.BI "\*(mTsel_state *" s ", sel_file *" f ,
-.BI "\h'\w'\*(mT'u'int " fd ", unsigned " mode ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(int " fd ", unsigned " mode ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBvoid sel_initfile('u
+.BI "void sel_initfile(sel_state *" s ", sel_file *" f ,
+.BI "  int " fd ", unsigned " mode ,
+.BI "  void (*" func ")(int " fd ", unsigned " mode ", void *" p ),
+.BI "  void *" p );
 .BI "void sel_addfile(sel_file *" f );
 .BI "void sel_force(sel_file *" f );
 .BI "void sel_rmfile(sel_file *" f );
 
-.ds mT \fBvoid sel_addtimer(
-.BI "\*(mTsel_state *" s ", sel_timer *" t ,
-.BI "\h'\w'\*(mT'u'struct timeval *" tv ,
-.BI "\h'\w'\*(mT'u'void (*" func ")(struct timeval *" tv ", void *" p ),
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBvoid sel_addtimer('u
+.BI "void sel_addtimer(sel_state *" s ", sel_timer *" t ,
+.BI "  struct timeval *" tv ,
+.BI "  void (*" func ")(struct timeval *" tv ", void *" p ),
+.BI "  void *" p );
 .BI "void sel_rmtimer(sel_timer *" t );
 
-.ds mT \fBvoid sel_addhook(
-.BI "\*(mTsel_state *" s ", sel_hook *" h ,
-.BI "\h'\w'\*(mT'u'sel_hookfn " before ", sel_hookfn " after ,
-.BI "\h'\w'\*(mT'u'void *" p );
+.ta \w'\fBvoid sel_addhook('u
+.BI "void sel_addtimer(sel_state *" s ", sel_hook *" h ,
+.BI "  sel_hookfn " before ", sel_hookfn " after ,
+.BI "  void *" p );
 .BI "void sel_rmhook(sel_hook *" h );
 
 .BI "int sel_fdmerge(fd_set *" dest ", fd_set *" fd ", int " maxfd );
index 032f9f1af2fc559d4e2bffcfb53af5fe08193dc4..5db9a71907c81ea5af6dca95abcaff37db3cd1aa 100644 (file)
@@ -16,9 +16,9 @@ selbuf \- line-buffering input selector
 .BI "void selbuf_enable(selbuf *" b );
 .BI "void selbuf_disable(selbuf *" b );
 .BI "void selbuf_setsize(selbuf *" b ", size_t " sz );
-.ds mT \fBvoid selbuf_init(
-.BI "\*(mTselbuf *" b ", sel_state *" s ", int " fd ,
-.BI "\h'\w'\*(mT'u'lbuf_func *" func ", void *" p );
+.ta \w'\fBvoid selbuf_init('u
+.BI "void selbuf_init(selbuf *" b ", sel_state *" s ", int " fd ,
+.BI "  lbuf_func *" func ", void *" p );
 .BI "void selbuf_destroy(selbuf *" b );
 .fi
 .SH DESCRIPTION
index 952c84f0ed7251b2898923adda8a32d0595e0a35..5a3a0ffb1760841656e78f7453184eaa3c232e01 100644 (file)
@@ -16,9 +16,9 @@ selpk \- packet-buffering input selector
 .BI "void selpk_enable(selpk *" pk );
 .BI "void selpk_disable(selpk *" pk );
 .BI "void selpk_want(selpk *" pk ", size_t " sz );
-.ds mT \fBvoid selpk_init(
-.BI "\*(mTselpk *" pk ", sel_state *" s ", int " fd ,
-.BI "\h'\w'\*(mT'u'pkbuf_func *" func ", void *" p );
+.ta \w'\fBvoid selpk_init('u
+.BI "void selpk_init(selpk *" pk ", sel_state *" s ", int " fd ,
+.BI "  pkbuf_func *" func ", void *" p );
 .BI "void selpk_destroy(selpk *" b );
 .fi
 .SH DESCRIPTION
index 68b58c9587605bd1ce7cea749be1fc01294577d5..725f859291bcb161962e67124876814824391bd8 100644 (file)
--- a/sel/sig.3
+++ b/sel/sig.3
@@ -11,9 +11,9 @@ sig \- more controlled signal handling
 
 .B "typedef struct { ...\& } sig;"
 
-.ds mT \fBvoid sig_add(
-.BI "\*(mTsig *" s ", int " n ,
-.BI "\h'\w'\*(mT'u'void (*" proc ")(int " n ", void *" p "), void *" p );
+.ta \w'\fBvoid sig_add('u
+.BI "void sig_add(sig *" s ", int " n ,
+.BI "  void (*" proc ")(int " n ", void *" p "), void *" p );
 .BI "void sig_remove(sig *" s );
 .BI "void sig_init(sel_state *" s );
 .fi
index 3b4261dbd9d6dde95a29648325b43e4541e344af..9a4cc547e3d29f6da024c9b95bdc546eb4188d05 100644 (file)
  */
 
 static int putch(void *out, int ch)
-  { buf *b = out; return (buf_putbyte(b, ch)); }
+  { buf *b = out; return (buf_putbyte(b, ch) ? -1 : 1); }
 
 static int putm(void *out, const char *p, size_t sz)
-  { buf *b = out; return (buf_put(b, p, sz)); }
+  { buf *b = out; return (buf_put(b, p, sz) ? -1 : sz); }
 
 static int nputf(void *out, size_t maxsz, const char *p, ...)
 {
index 10b52cf1ac0aa53e4f480239f8342cf36b67d4bd..3b3ef7ce2edf854f889a46b71319e74079e0d83d 100644 (file)
@@ -19,7 +19,9 @@
 ..
 .ie t .ds o \(bu
 .el .ds o o
+.
 .TH buf 3 "23 September 2005" "Straylight/Edgeware" "mLib utilities library"
+.
 .SH NAME
 buf \- reading and writing stuff in buffers
 .\" @BBASE
@@ -32,16 +34,64 @@ buf \- reading and writing stuff in buffers
 .\" @BBAD
 .\" @BOK
 .\" @BENSURE
+
+.\" @DBBASE
+.\" @DBLIM
+.\" @DBCUR
+.\" @DBSZ
+.\" @DBLEN
+.\" @DBLEFT
+.\" @DBSTEP
+.\" @DBBAD
+.\" @DBOK
+.\" @DBENSURE
 .
 .\" @buf_init
+.
+.\" @dbuf_create
+.\" @dbuf_reset
+.\" @dbuf_destroy
+.\" @DBCREATE
+.\" @DBRESET
+.\" @DBDESTROY
+.\" @DBUF_INIT
+.\" @DBUF_BUF
+.
 .\" @buf_break
 .\" @buf_flip
 .\" @buf_ensure
+.\" @buf_tryextend
 .\" @buf_get
 .\" @buf_put
+.\" @dbuf_break
+.\" @dbuf_flip
+.\" @dbuf_ensure
+.\" @dbuf_tryextend
+.\" @dbuf_get
+.\" @dbuf_put
 .
 .\" @buf_getbyte
 .\" @buf_putbyte
+.\" @dbuf_getbyte
+.\" @dbuf_putbyte
+.
+.\" @buf_getf64
+.\" @buf_getf64l
+.\" @buf_getf64b
+.\" @buf_putf64
+.\" @buf_putf64l
+.\" @buf_putf64b
+.\" @dbuf_getf64
+.\" @dbuf_getf64l
+.\" @dbuf_getf64b
+.\" @dbuf_putf64
+.\" @dbuf_putf64l
+.\" @dbuf_putf64b
+.
+.\" @buf_putstrf
+.\" @buf_vputstrf
+.\" @dbuf_putstrf
+.\" @dbuf_vputstrf
 .
 .\" @buf_getu8
 .\" @buf_getu16
@@ -53,6 +103,28 @@ buf \- reading and writing stuff in buffers
 .\" @buf_getu32
 .\" @buf_getu32b
 .\" @buf_getu32l
+.\" @buf_getu64
+.\" @buf_getu64b
+.\" @buf_getu64l
+.\" @buf_getk64
+.\" @buf_getk64b
+.\" @buf_getk64l
+.\" @dbuf_getu8
+.\" @dbuf_getu16
+.\" @dbuf_getu16b
+.\" @dbuf_getu16l
+.\" @dbuf_getu24
+.\" @dbuf_getu24b
+.\" @dbuf_getu24l
+.\" @dbuf_getu32
+.\" @dbuf_getu32b
+.\" @dbuf_getu32l
+.\" @dbuf_getu64
+.\" @dbuf_getu64b
+.\" @dbuf_getu64l
+.\" @dbuf_getk64
+.\" @dbuf_getk64b
+.\" @dbuf_getk64l
 .
 .\" @buf_putu8
 .\" @buf_putu16
@@ -64,6 +136,28 @@ buf \- reading and writing stuff in buffers
 .\" @buf_putu32
 .\" @buf_putu32b
 .\" @buf_putu32l
+.\" @buf_putu64
+.\" @buf_putu64b
+.\" @buf_putu64l
+.\" @buf_putk64
+.\" @buf_putk64b
+.\" @buf_putk64l
+.\" @dbuf_putu8
+.\" @dbuf_putu16
+.\" @dbuf_putu16b
+.\" @dbuf_putu16l
+.\" @dbuf_putu24
+.\" @dbuf_putu24b
+.\" @dbuf_putu24l
+.\" @dbuf_putu32
+.\" @dbuf_putu32b
+.\" @dbuf_putu32l
+.\" @dbuf_putu64
+.\" @dbuf_putu64b
+.\" @dbuf_putu64l
+.\" @dbuf_putk64
+.\" @dbuf_putk64b
+.\" @dbuf_putk64l
 .
 .\" @buf_getbuf8
 .\" @buf_getbuf16
@@ -75,7 +169,24 @@ buf \- reading and writing stuff in buffers
 .\" @buf_getbuf32
 .\" @buf_getbuf32b
 .\" @buf_getbuf32l
+.\" @buf_getbuf64
+.\" @buf_getbuf64b
+.\" @buf_getbuf64l
 .\" @buf_getbufz
+.\" @dbuf_getbuf8
+.\" @dbuf_getbuf16
+.\" @dbuf_getbuf16b
+.\" @dbuf_getbuf16l
+.\" @dbuf_getbuf24
+.\" @dbuf_getbuf24b
+.\" @dbuf_getbuf24l
+.\" @dbuf_getbuf32
+.\" @dbuf_getbuf32b
+.\" @dbuf_getbuf32l
+.\" @dbuf_getbuf64
+.\" @dbuf_getbuf64b
+.\" @dbuf_getbuf64l
+.\" @dbuf_getbufz
 .
 .\" @buf_putbuf8
 .\" @buf_putbuf16
@@ -87,8 +198,26 @@ buf \- reading and writing stuff in buffers
 .\" @buf_putbuf32
 .\" @buf_putbuf32b
 .\" @buf_putbuf32l
+.\" @buf_putbuf64
+.\" @buf_putbuf64b
+.\" @buf_putbuf64l
 .\" @buf_putbufz
+.\" @dbuf_putbuf8
+.\" @dbuf_putbuf16
+.\" @dbuf_putbuf16b
+.\" @dbuf_putbuf16l
+.\" @dbuf_putbuf24
+.\" @dbuf_putbuf24b
+.\" @dbuf_putbuf24l
+.\" @dbuf_putbuf32
+.\" @dbuf_putbuf32b
+.\" @dbuf_putbuf32l
+.\" @dbuf_putbuf64
+.\" @dbuf_putbuf64b
+.\" @dbuf_putbuf64l
+.\" @dbuf_putbufz
 .
+.\" @buf_getmem8
 .\" @buf_getmem16
 .\" @buf_getmem16b
 .\" @buf_getmem16l
@@ -98,8 +227,24 @@ buf \- reading and writing stuff in buffers
 .\" @buf_getmem32
 .\" @buf_getmem32b
 .\" @buf_getmem32l
-.\" @buf_getmem8
+.\" @buf_getmem64
+.\" @buf_getmem64b
+.\" @buf_getmem64l
 .\" @buf_getmemz
+.\" @dbuf_getmem8
+.\" @dbuf_getmem16
+.\" @dbuf_getmem16b
+.\" @dbuf_getmem16l
+.\" @dbuf_getmem24
+.\" @dbuf_getmem24b
+.\" @dbuf_getmem24l
+.\" @dbuf_getmem32
+.\" @dbuf_getmem32b
+.\" @dbuf_getmem32l
+.\" @dbuf_getmem64
+.\" @dbuf_getmem64b
+.\" @dbuf_getmem64l
+.\" @dbuf_getmemz
 .
 .\" @buf_putmem8
 .\" @buf_putmem16
@@ -111,7 +256,24 @@ buf \- reading and writing stuff in buffers
 .\" @buf_putmem32
 .\" @buf_putmem32b
 .\" @buf_putmem32l
+.\" @buf_putmem64
+.\" @buf_putmem64b
+.\" @buf_putmem64l
 .\" @buf_putmemz
+.\" @dbuf_putmem8
+.\" @dbuf_putmem16
+.\" @dbuf_putmem16b
+.\" @dbuf_putmem16l
+.\" @dbuf_putmem24
+.\" @dbuf_putmem24b
+.\" @dbuf_putmem24l
+.\" @dbuf_putmem32
+.\" @dbuf_putmem32b
+.\" @dbuf_putmem32l
+.\" @dbuf_putmem64
+.\" @dbuf_putmem64b
+.\" @dbuf_putmem64l
+.\" @dbuf_putmemz
 .
 .\" @buf_putstr8
 .\" @buf_putstr16
@@ -123,7 +285,24 @@ buf \- reading and writing stuff in buffers
 .\" @buf_putstr32
 .\" @buf_putstr32b
 .\" @buf_putstr32l
+.\" @buf_putstr64
+.\" @buf_putstr64b
+.\" @buf_putstr64l
 .\" @buf_putstrz
+.\" @dbuf_putstr8
+.\" @dbuf_putstr16
+.\" @dbuf_putstr16b
+.\" @dbuf_putstr16l
+.\" @dbuf_putstr24
+.\" @dbuf_putstr24b
+.\" @dbuf_putstr24l
+.\" @dbuf_putstr32
+.\" @dbuf_putstr32b
+.\" @dbuf_putstr32l
+.\" @dbuf_putstr64
+.\" @dbuf_putstr64b
+.\" @dbuf_putstr64l
+.\" @dbuf_putstrz
 .
 .\" @buf_getdstr8
 .\" @buf_getdstr16
@@ -135,7 +314,24 @@ buf \- reading and writing stuff in buffers
 .\" @buf_getdstr32
 .\" @buf_getdstr32b
 .\" @buf_getdstr32l
+.\" @buf_getdstr64
+.\" @buf_getdstr64b
+.\" @buf_getdstr64l
 .\" @buf_getdstrz
+.\" @dbuf_getdstr8
+.\" @dbuf_getdstr16
+.\" @dbuf_getdstr16b
+.\" @dbuf_getdstr16l
+.\" @dbuf_getdstr24
+.\" @dbuf_getdstr24b
+.\" @dbuf_getdstr24l
+.\" @dbuf_getdstr32
+.\" @dbuf_getdstr32b
+.\" @dbuf_getdstr32l
+.\" @dbuf_getdstr64
+.\" @dbuf_getdstr64b
+.\" @dbuf_getdstr64l
+.\" @dbuf_getdstrz
 .
 .\" @buf_putdstr8
 .\" @buf_putdstr16
@@ -147,14 +343,154 @@ buf \- reading and writing stuff in buffers
 .\" @buf_putdstr32
 .\" @buf_putdstr32b
 .\" @buf_putdstr32l
+.\" @buf_putdstr64
+.\" @buf_putdstr64b
+.\" @buf_putdstr64l
 .\" @buf_putdstrz
+.\" @dbuf_putdstr8
+.\" @dbuf_putdstr16
+.\" @dbuf_putdstr16b
+.\" @dbuf_putdstr16l
+.\" @dbuf_putdstr24
+.\" @dbuf_putdstr24b
+.\" @dbuf_putdstr24l
+.\" @dbuf_putdstr32
+.\" @dbuf_putdstr32b
+.\" @dbuf_putdstr32l
+.\" @dbuf_putdstr64
+.\" @dbuf_putdstr64b
+.\" @dbuf_putdstr64l
+.\" @dbuf_putdstrz
+.
+.\" @buf_putstrf8
+.\" @buf_putstrf16
+.\" @buf_putstrf16b
+.\" @buf_putstrf16l
+.\" @buf_putstrf24
+.\" @buf_putstrf24b
+.\" @buf_putstrf24l
+.\" @buf_putstrf32
+.\" @buf_putstrf32b
+.\" @buf_putstrf32l
+.\" @buf_putstrf64
+.\" @buf_putstrf64b
+.\" @buf_putstrf64l
+.\" @buf_putstrfz
+.\" @buf_vputstrf8
+.\" @buf_vputstrf16
+.\" @buf_vputstrf16b
+.\" @buf_vputstrf16l
+.\" @buf_vputstrf24
+.\" @buf_vputstrf24b
+.\" @buf_vputstrf24l
+.\" @buf_vputstrf32
+.\" @buf_vputstrf32b
+.\" @buf_vputstrf32l
+.\" @buf_vputstrf64
+.\" @buf_vputstrf64b
+.\" @buf_vputstrf64l
+.\" @buf_vputstrfz
+.\" @dbuf_putstrf8
+.\" @dbuf_putstrf16
+.\" @dbuf_putstrf16b
+.\" @dbuf_putstrf16l
+.\" @dbuf_putstrf24
+.\" @dbuf_putstrf24b
+.\" @dbuf_putstrf24l
+.\" @dbuf_putstrf32
+.\" @dbuf_putstrf32b
+.\" @dbuf_putstrf32l
+.\" @dbuf_putstrf64
+.\" @dbuf_putstrf64b
+.\" @dbuf_putstrf64l
+.\" @dbuf_putstrfz
+.\" @dbuf_vputstrf8
+.\" @dbuf_vputstrf16
+.\" @dbuf_vputstrf16b
+.\" @dbuf_vputstrf16l
+.\" @dbuf_vputstrf24
+.\" @dbuf_vputstrf24b
+.\" @dbuf_vputstrf24l
+.\" @dbuf_vputstrf32
+.\" @dbuf_vputstrf32b
+.\" @dbuf_vputstrf32l
+.\" @dbuf_vputstrf64
+.\" @dbuf_vputstrf64b
+.\" @dbuf_vputstrf64l
+.\" @dbuf_vputstrfz
+.
+.\" @BUF_ENCLOSETAG
+.\" @BUF_ENCLOSEITAG
+.\" @BUF_ENCLOSEKTAG
+.\" @BUF_ENCLOSEZTAG
+.\" @BUF_ENCLOSE8
+.\" @BUF_ENCLOSE16
+.\" @BUF_ENCLOSE16_L
+.\" @BUF_ENCLOSE16_B
+.\" @BUF_ENCLOSE24
+.\" @BUF_ENCLOSE24_L
+.\" @BUF_ENCLOSE24_B
+.\" @BUF_ENCLOSE32
+.\" @BUF_ENCLOSE32_L
+.\" @BUF_ENCLOSE32_B
+.\" @BUF_ENCLOSE64
+.\" @BUF_ENCLOSE64_L
+.\" @BUF_ENCLOSE64_B
+.\" @BUF_ENCLOSEZ
+.\" @DBUF_ENCLOSETAG
+.\" @DBUF_ENCLOSEITAG
+.\" @DBUF_ENCLOSEKTAG
+.\" @DBUF_ENCLOSEZTAG
+.\" @DBUF_ENCLOSE8
+.\" @DBUF_ENCLOSE16
+.\" @DBUF_ENCLOSE16_L
+.\" @DBUF_ENCLOSE16_B
+.\" @DBUF_ENCLOSE24
+.\" @DBUF_ENCLOSE24_L
+.\" @DBUF_ENCLOSE24_B
+.\" @DBUF_ENCLOSE32
+.\" @DBUF_ENCLOSE32_L
+.\" @DBUF_ENCLOSE32_B
+.\" @DBUF_ENCLOSE64
+.\" @DBUF_ENCLOSE64_L
+.\" @DBUF_ENCLOSE64_B
+.\" @DBUF_ENCLOSEZ
+.
 .SH SYNOPSIS
 .nf
 .B "#include <mLib/dstr.h>"
 
 .B "typedef struct { ...\& } buf;"
+.B "typedef struct { ...\& } dbuf;"
 
 .BI "void buf_init(buf *" b ", void *" p ", size_t " sz );
+.BI "void dbuf_create(dbuf *" db );
+.BI "void dbuf_reset(dbuf *" db );
+.BI "void dbuf_destroy(dbuf *" db );
+.BI "buf *DBUF_BUF(dbuf *" db );
+.BI "void DBCREATE(dbuf *" db );
+.BI "void DBRESET(dbuf *" db );
+.BI "void DBDESTROY(dbuf *" db );
+.B "#define DBUF_INIT ..."
+
+.fi
+All of the following functions and macros exist in two variants:
+one with a name beginning
+.BR buf_ ,
+.BR B ,
+or
+.BR BUF_ ,
+and taking a first argument of type
+.BR "buf *" ;
+and a corresponding similarly named version with name beginning instead
+.BR dbuf_ ,
+.BR DB ,
+or
+.BR DBUF_ ,
+and taking a first argument of type
+.BR "dbuf *" .
+.nf
+
 .BI "void buf_flip(buf *" b );
 .BI "octet *BBASE(buf *" b );
 .BI "octet *BLIM(buf *" b );
@@ -162,12 +498,15 @@ buf \- reading and writing stuff in buffers
 .BI "ptrdiff_t BSZ(buf *" b );
 .BI "ptrdiff_t BLEN(buf *" b );
 .BI "ptrdiff_t BLEFT(buf *" b );
+.BI "void BFLIP(buf *" b );
 
 .BI "int buf_break(buf *" b );
+.BI "int BBREAK(buf *" b );
 .BI "int BBAD(buf *" b );
 .BI "int BOK(buf *" b );
 
 .BI "int buf_ensure(buf *" b ", size_t " sz );
+.BI "int buf_tryextend(buf *" b ", size_t " sz );
 .BI "int BENSURE(buf *" b ", size_t " sz );
 .BI "octet *BSTEP(buf *" b ", size_t " sz );
 
@@ -175,23 +514,144 @@ buf \- reading and writing stuff in buffers
 .BI "void *buf_put(buf *" b ", const void *" p ", size_t " sz );
 
 .BI "int buf_getbyte(buf *" b );
-.BI "int buf_putbyte(buf *" b ", int ch" );
-.BI "int buf_getu" suff "(buf *" b ", uint" suff " *" w );
+.BI "int buf_putbyte(buf *" b ", int " ch );
+
+.BI "int buf_putstr(buf *" b ", const char *" p ", ...);"
+.BI "int buf_vputstr(buf *" b ", const char *" p ", va_list *" ap );
+
+.fi
+For
+.I suff
+in
+.BR 8 ,
+.BR 16 ,
+.BR 16l ,
+.BR 16b ,
+.BR 24 ,
+.BR 24l ,
+.BR 24b ,
+.BR 32 ,
+.BR 32l ,
+and
+.BR 32b ,
+and, if a 64-bit integer type is available,
+.BR 64 ,
+.BR 64l ,
+and
+.BR 64b :
+.nf
 .BI "int buf_putu" suff "(buf *" b ", uint" suff " " w );
-.BI "void *buf_getmem" suff "(buf *" b ", size_t *" sz );
-.BI "int buf_putmem" suff "(buf *" b ", const void *" p ", size_t " sz );
-.BI "int buf_getbuf" suff "(buf *" b ", buf *" bb );
-.BI "int buf_putbuf" suff "(buf *" b ", buf *" bb );
-.BI "int buf_getdstr" suff "(buf *" b ", dstr *" d );
-.BI "int buf_putdstr" suff "(buf *" b ", dstr *" d );
+.BI "int buf_getu" suff "(buf *" b ", uint" suff " *" w );
+
+.fi
+For
+.I suff
+in
+.BR 64 ,
+.BR 64l ,
+and
+.BR 64b :
+.nf
+.BI "int buf_putk" suff "(buf *" b ", kludge64 " w );
+.BI "int buf_getk" suff "(buf *" b ", kludge64 *" w );
+
+.ta 2n
+.BI "BUF_ENCLOSETAG(" tag ", buf *" b ", size_t " mk ", " check ", " poke ", size_t " lensz )
+.I "   body"
+.BI "BUF_ENCLOSEITAG(" tag ", buf *" b ", size_t " mk ", " W )
+.I "   body"
+.BI "BUF_ENCLOSEKTAG(" tag ", buf *" b ", size_t " mk ", " W )
+.I "   body"
+.BI "BUF_ENCLOSEZTAG(" tag ", buf *" b )
+.I "   body"
+
+.fi
+For
+.I suff
+in
+.BR 8 ,
+.BR 16 ,
+.BR 16_L ,
+.BR 16_B ,
+.BR 24 ,
+.BR 24_L ,
+.BR 24_B ,
+.BR 32 ,
+.BR 32_L ,
+.BR 32_B ,
+.BR 64 ,
+.BR 64_L ,
+and
+.BR 64_B ,
+.nf
+.ta 2n
+.BI "BUF_ENCLOSE" suff "(buf *" b ", size_t " mk )
+.I "   body"
+
+.BI "BUF_ENCLOSEZ(buf *" b )
+.I "   body"
+
+.fi
+For
+.I suff
+in
+.BR 8 ,
+.BR 16 ,
+.BR 16l ,
+.BR 16b ,
+.BR 24 ,
+.BR 24l ,
+.BR 24b ,
+.BR 32 ,
+.BR 32l ,
+.BR 32b ,
+.BR 64 ,
+.BR 64l ,
+.BR 64b ,
+and
+.BR z :
+.nf
 .BI "int buf_putstr" suff "(buf *" b ", const char *" p );
+.BI "int dbuf_putstr" suff "(dbuf *" db ", const char *" p );
+.BI "int buf_putstr" suff "(buf *" b ", const char *" p ", ...);"
+.BI "int dbuf_putstr" suff "(dbuf *" db ", const char *" p ", ...);"
+.BI "int buf_vputstr" suff "(buf *" b ", const char *" p ", va_list *" ap );
+.BI "int dbuf_vputstr" suff "(dbuf *" db ", const char *" p ", va_list *" ap );
+.BI "int buf_putdstr" suff "(buf *" b ", dstr *" d );
+.BI "int dbuf_putdstr" suff "(dbuf *" db ", dstr *" d );
+.BI "int buf_getdstr" suff "(buf *" b ", dstr *" d );
+.BI "int dbuf_getdstr" suff "(dbuf *" db ", dstr *" d );
+.BI "int buf_putbuf" suff "(buf *" b ", buf *" bb );
+.BI "int dbuf_putbuf" suff "(dbuf *" db ", buf *" bb );
+.BI "int buf_getbuf" suff "(buf *" b ", buf *" bb );
+.BI "int dbuf_getbuf" suff "(dbuf *" db ", buf *" bb );
+.BI "int buf_putmem" suff "(buf *" b ", const void *" p ", size_t " sz );
+.BI "int dbuf_putmem" suff "(dbuf *" db ", const void *" p ", size_t " sz );
+.BI "void *buf_getmem" suff "(buf *" b ", size_t *" sz );
+.BI "void d*buf_getmem" suff "(dbuf *" db ", size_t *" sz );
+
 .fi
+For
+.I suff
+in
+.BR 64 ,
+.BR 64l ,
+and
+.BR 64b :
+.nf
+.BI "int buf_putf" suff "(buf *" b ", double " x );
+.BI "int dbuf_putf" suff "(dbuf *" db ", double " x );
+.BI "int buf_getf" suff "(buf *" b ", double *" x );
+.BI "int dbuf_getf" suff "(dbuf *" db ", double *" x );
+.fi
+.
 .SH DESCRIPTION
 The
 .B buf
 interface allows relatively convenient reading and writing of structured
 binary data from and to fixed-size memory buffers.  It's useful for
 formatting and parsing down network data packets, for example.
+.
 .SS "Buffer basics"
 A buffer has three important pointers associated with it:
 .TP
@@ -267,6 +727,10 @@ and to set
 .I current
 to
 .IR base .
+There is a macro version,
+.BR BFLIP ,
+which does the same thing,
+but it may evaluate its buffer argument multiple times.
 .PP
 A buffer can be
 .IR broken ,
@@ -279,12 +743,19 @@ call and just check the brokenness state at the end of their run.
 .PP
 The function
 .B buf_break
-will break a buffer.  The macro
+or its
+macro equivalent
+.B BBREAK
+will break a buffer:
+the function returns \-1 as a possible, but minor, convenience;
+the macro expands to a statement and cannot return a value.
+The macro
 .B BBAD
 reports true (nonzero) if its buffer argument is broken, or false (zero)
 otherwise; its counterpart
 .B BOK
 reports true if the buffer is OK, and false if it is broken.
+.
 .SS "Low-level buffer access"
 Access to the data in the buffer is usually sequential.  The
 .B BENSURE
@@ -329,6 +800,7 @@ function writes
 bytes of data starting at
 .I p
 to the buffer.  If it succeeded, it returns 0; otherwise it returns \-1.
+.
 .SS "Formatted buffer access"
 The function
 .B buf_getbyte
@@ -360,8 +832,65 @@ given, and returns zero; on failure, it returns \-1.  The function
 .BI buf_putu suff
 write an integer.  It returns zero on success or \-1 on failure.
 .PP
+For (portability to) platforms without 64-bit integers, the functions
+.B buf_getk64
+and
+.B buf_putk64
+(and
+.RB ` l '-
+and
+.RB ` b '-suffixed
+variants) perform the necessary functionality, but acting on the
+.B kludge64
+type; see
+.BR bits (3).
+.PP
+The functions
+.BR buf_getf64 ,
+.BR buf_getf64l ,
+and
+.BR buf_getf64b
+read 64-bit floating-point values
+in IEEE\ 754 Binary64 format
+from the buffer;
+as usual, the suffix indicates the byte ordering convention.
+On success, they store the result in
+.BI *x
+and return zero;
+on failure, they break the buffer and return zero.
+The functions
+.BR buf_putf64 ,
+.BR buf_putf64l ,
+and
+.BR buf_putf64b
+write floating-point numbers
+in IEEE\ 754 Binary64 format
+from the buffer.
+On success, they return zero; on failure, they return \-1.
+Note that these functions use IEEE\ 754 format
+even if this is not the platform-native floating-point representation.
+.PP
+The function
+.B buf_putstrf
+processes a
+.BR printf (3)-like
+format string and arguments,
+writing the output to the buffer.
+The function
+.B buf_vputstrf
+does the same,
+except that it reads arguments from a
+.B va_list
+captured argument tail,
+leaving the tail ready to read the next unprocessed argument.
+Both functions return the number of bytes written on success
+or \-1 on failure.
+Note that these functions apply no length framing or termination.
+.PP
 Functions which deal with block lengths assume the length is prefixed to
-the data, and don't include themselves.  They also have an additional
+the data, and don't include themselves.  They come in all of the integer
+size variants, including 64-bits even on platforms without 64-bit integers;
+they also have an additional
 .RB ` z '
 variant, which deals with zero-terminated data.  No checks are done on
 writing that the data written contains no zero bytes.
@@ -401,8 +930,242 @@ function
 .BI buf_putstr suff
 writes a standard C null-terminated string to a buffer.  All these
 functions return zero on success or \-1 on failure.
+.PP
+The function
+.BI buf_putstrf suff
+processes a
+.BR printf (3)-like
+format string and arguments,
+writing the output to the buffer.
+The function
+.BI buf_vputstrf suff
+does the same,
+except that it reads arguments from a
+.B va_list
+captured argument tail,
+leaving the tail ready to read the next unprocessed argument.
+Both functions return the number of bytes written on success
+or \-1 on failure.
+These functions add framing around the output:
+either a length prefix, or a trailing zero byte.
+.PP
+The
+.BI BUF_ENCLOSE suff
+macros are syntactically statement heads.
+(Notice that these macros use
+.RB ` _L '
+and
+.RB ` _B '
+suffixes for little- and big-endian byte order.)
+They leave space in the buffer for appropriate length framing,
+and execute the following
+.I body
+statement
+(which, of course, can be a compound statement enclosed in braces).
+When the
+.I body
+completes, the macro fills in space
+with the length of material written by the
+.IR body .
+The
+.I mk
+argument should be a variable of type
+.B size_t
+which will be overwritten by the macro.
+If the material is so large that its won't fit in the space
+then the buffer is broken.
+The
+.B BUF_ENCLOSEZ
+macro is similar,
+except that it just writes a terminating zero byte
+after whatever material was written by the
+.IR body .
+.PP
+The
+.BR BUF_ENCLOSE ...\&
+macros are based on lower-level machinery.
+The
+.B BUF_ENCLOSEITAG
+macro takes an additional argument
+.IR W ;
+it leaves
+.BI SZ_ W
+bytes for the length,
+checks that the length doesn't exceed
+.BI MASK W \fR,
+and stores the length using
+.BI STORE W \fR;
+all of these constants and macros are defined in
+.BR <mLib/bits.h> .
+The
+.B BUF_ENCLOSEKTAG
+is similar, except that it uses the
+.B kludge64
+machinery to handle 64-bit length fields.
+The
+.B BUF_ENCLOSEZTAG
+macro is superficially similar,
+but much simpler,
+since it all it does is write a zero byte after its
+.I body
+completes.
+All of those macros also take an additional
+.I tag
+argument
+used to scope the internal labels they construct:
+see
+.BR control (3)
+for the details on how this works.
+.PP
+The
+.B BUF_ENCLOSEITAG
+and
+.B BUF_ENCLOSEKTAG
+macros are themselves built from a lower-level macro named
+.BR BUF_ENCLOSETAG .
+In place of the
+.I W
+argument, it takes three arguments:
+.I check
+is an expression which should evaluate true if the length
+.B _delta
+can be represented;
+.I poke
+is a macro, invoked as
+.IB poke "(unsigned char *" p ", " size_t n ")" \fR,
+which should store
+.I n
+at address
+.IR p ,
+formatted in whatever way is appropriate;
+and
+.I lensz
+is the amount of space, in bytes, to save for the length.
+.
+.SS "Dynamic buffers"
+The type
+.B dbuf
+is a
+.IR "dynamic buffer" .
+It contains a buffer structure,
+accessible using the
+p.B DBUF_BUF
+macro.
+The ordinary buffer functions and macros can be used on this buffer,
+though, for convenience,
+there are similarly named functions and macros
+which accept a
+.B dbuf
+argument directly.
+There is
+.I "no difference"
+between the behaviour of the
+.B "buf"
+and
+.B "dbuf"
+functions.
+.PP
+A dynamic buffer is created by statically initializing it with
+.BR DBUF_INIT ,
+or by calling
+.BR dbuf_create
+or its macro equivalent
+.BR DBCREATE .
+The memory backing a dynamic buffer can be freed by
+.BR dbuf_destroy
+or the macro equivalent
+.BR DBDESTROY ;
+these leave the buffer in the state established by initialization:
+the buffer holds no resources, but is ready for immediate use.
+.PP
+A dynamic buffer contains an 
+.B buf
+buffer,
+called its
+.I underlying
+buffer.
+The underlying buffer is accessible through the
+.B DBUF_BUF
+macro.
+All of the above functions and macros can be applied
+to a dynamic buffer's underlying buffer.
+As a convenience,
+corresponding to each of the functions and macros described above,
+there is a version named with an initial
+.RB ` d '
+or
+.RB ` D '
+as appropriate,
+which accepts a pointer to a dynamic buffer
+rather than an ordinary buffer,
+and acts on its underlying buffer.
+Note that these functions are in no way special.
+A dynamic buffer will grow automatically
+in response to either kind of functions.
+.PP
+A freshly created buffer is in
+.I write
+mode,
+and is empty, with.
+In this state, it will automatically extend its backing storage
+in response to
+.B BENSURE
+calls, rather than breaking.
+As a result,
+an
+.I "a priori"
+unpredictable amount of data can be written to a dynamic buffer
+and it will automatically grow as necessary to accommodate it.
+Of course, the
+.B BSZ
+and
+.B BLEFT
+queries are somewhat meaningless when applied to dynamic buffers \(en
+though perfectly valid.
+The critical function for this is
+.B buf_tryextend
+(also accessible as
+.BR dbuf_tryextend )
+which attempts to arrange that at least
+.I sz
+unused bytes are available in the buffer \(en
+i.e., that
+.B BLEFT
+would return at least
+.IR sz .
+If it succeeds, it returns zero;
+it will fail if the buffer is not in write mode,
+or if the buffer is not dynamic,
+in which case it returns \-1.
+It is unlikely that applications will call this function directly.
+.PP
+The
+.B buf_flip
+(or its macro equivalent)
+switches the buffer to
+.I read
+mode,
+in addition to its usual behaviour of
+setting the buffer's limit to its current position
+and its current position to its base.
+In read mode, a dynamic buffer will no longer grow dynamically,
+as one would expect.
+.PP
+The
+.B dbuf_reset
+function,
+and its macro equivalent
+.B DBRESET
+(which may evaluate its argument multiple times)
+will return a dynamic buffer to write mode,
+and also restore its current position to its base and
+clear its broken flag.
+.
 .SH "SEE ALSO"
+.BR bits (3),
+.BR control (3),
 .BR dstr (3),
 .BR mLib (3).
+.
 .SH AUTHOR
 Mark Wooding, <mdw@distorted.org.uk>
index 9efc4fcecbd59b37d1a13e7168f2e51ce13489e1..88934c9f23ce44ffaf2d25f04bc5d1298969dc81 100644 (file)
 
 /*----- Header files ------------------------------------------------------*/
 
-#include <assert.h>
 #include <string.h>
 
 #include "buf.h"
+#include "growbuf.h"
 #include "macros.h"
 
 /*----- Main code ---------------------------------------------------------*/
@@ -63,11 +63,7 @@ void buf_init(buf *b, void *p, size_t sz)
  *             and ready for writing.
  */
 
-void dbuf_create(dbuf *db)
-{
-  db->_b.base = db->_b.p = db->_b.limit = 0; db->_b.f = BF_ALLOC | BF_WRITE;
-  db->a = &arena_stdlib; db->sz = 0;
-}
+void dbuf_create(dbuf *db) { DBCREATE(db); }
 
 /* --- @dbuf_reset@ --- *
  *
@@ -89,11 +85,7 @@ void dbuf_reset(dbuf *db) { DBRESET(db); }
  * Use:                Release all of the resources held by a dynamic buffer.
  */
 
-void dbuf_destroy(dbuf *db)
-{
-  if (db->_b.base) x_free(db->a, db->_b.base);
-  dbuf_create(db);
-}
+void dbuf_destroy(dbuf *db) { DBDESTROY(db); }
 
 /* --- @{,d}buf_break@ --- *
  *
@@ -104,8 +96,8 @@ void dbuf_destroy(dbuf *db)
  * Use:                Marks a buffer as broken.
  */
 
-int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); }
-int (dbuf_break)(dbuf *db) { return (dbuf_break(db)); }
+int buf_break(buf *b) { BBREAK(b); return (-1); }
+int (dbuf_break)(dbuf *db) { DBBREAK(db); return (-1); }
 
 /* --- @{,d}buf_flip@ --- *
  *
@@ -118,7 +110,7 @@ int (dbuf_break)(dbuf *db) { return (dbuf_break(db)); }
  */
 
 void buf_flip(buf *b) { BFLIP(b); }
-void (dbuf_flip)(dbuf *db) { dbuf_flip(db); }
+void (dbuf_flip)(dbuf *db) { DBFLIP(db); }
 
 /* --- @{,d}buf_ensure@ --- *
  *
@@ -147,22 +139,17 @@ int (dbuf_ensure)(dbuf *db, size_t sz) { return (dbuf_ensure(db, sz)); }
 int buf_tryextend(buf *b, size_t sz)
 {
   dbuf *db;
-  size_t newsz, len;
-
-  if (~b->f&(BF_ALLOC | BF_WRITE))
-    { b->f |= BF_BROKEN; return (-1); }
-  db = (dbuf *)b;
-  len = BLEN(&db->_b); sz += len;
-  if (db->sz >= sz)
-    newsz = db->sz;
-  else {
-    newsz = db->sz ? 2*db->sz : 64;
-    while (newsz < sz) { assert(newsz < ((size_t)-1)/2); newsz *= 2; }
-    if (!db->_b.base) db->_b.base = x_alloc(db->a, newsz);
-    else db->_b.base = x_realloc(db->a, db->_b.base, newsz, db->sz);
-    db->_b.p = db->_b.base + len; db->sz = newsz;
+  size_t want, len;
+
+  if (sz <= BLEFT(b)) return (0);
+  if (~b->f&(BF_ALLOC | BF_WRITE)) { b->f |= BF_BROKEN; return (-1); }
+
+  db = (dbuf *)b; len = DBLEN(db); want = sz + len;
+  if (db->sz < want) {
+    GROWBUF_EXTEND(db->a, db->_b.base, db->sz, want, 64, 1);
+    db->_b.p = db->_b.base + len;
   }
-  db->_b.limit = db->_b.base + newsz;
+  db->_b.limit = db->_b.base + db->sz;
   return (0);
 }
 int (dbuf_tryextend)(dbuf *db, size_t sz)
@@ -374,10 +361,7 @@ static int findz(buf *b, size_t *nn)
 {
   octet *p;
 
-  if ((p = memchr(BCUR(b), 0, BLEFT(b))) == 0) {
-    buf_break(b);
-    return (-1);
-  }
+  if ((p = memchr(BCUR(b), 0, BLEFT(b))) == 0) { BBREAK(b); return (-1); }
   *nn = p - BCUR(b) + 1;
   return (0);
 }
@@ -423,7 +407,7 @@ static void *getmem_k64(buf *b, size_t *nn_out, kludge64 k)
   size_t n;
 
   ASSIGN64(szmax, (size_t)-1);
-  if (CMP64(k, >, szmax)) { buf_break(b); return (-1); }
+  if (CMP64(k, >, szmax)) { BBREAK(b); return (-1); }
   n = GET64(size_t, k); *nn_out = n; return (buf_get(b, n));
 }
 
@@ -478,7 +462,7 @@ void *(dbuf_getmem64b)(dbuf *db, size_t *nn)
   {                                                                    \
     MUFFLE_WARNINGS_STMT                                               \
       (CLANG_WARNING("-Wtautological-constant-out-of-range-compare"),  \
-       { assert(sz <= MASK##W); });                                    \
+       { if (sz > MASK##W) { BBREAK(b); return (-1); } });             \
     if (buf_putu##w(b, sz) || buf_put(b, p, sz))                       \
       return (-1);                                                     \
     return (0);                                                                \
@@ -526,7 +510,7 @@ int buf_putmemz(buf *b, const void *p, size_t n)
 {
   octet *q;
 
-  assert(!memchr(p, 0, n));
+  if (memchr(p, 0, n)) { BBREAK(b); return (-1); }
   if ((q = buf_get(b, n + 1)) == 0)
     return (-1);
   memcpy(q, p, n);
index 7dd555541257a3d48fdf8aec32293787206ae432..dd5f6e7129373fb8e2edbef479fd65b06d0630ff 100644 (file)
@@ -142,6 +142,11 @@ extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/);
  */
 
 extern void dbuf_create(dbuf */*db*/);
+#define DBCREATE(db) do {                                              \
+  (db)->_b.base = (db)->_b.p = (db)->_b.limit = 0;                     \
+  (db)->_b.f = BF_ALLOC | BF_WRITE;                                    \
+  (db)->a = &arena_stdlib; (db)->sz = 0;                               \
+} while (0)
 
 /* --- @dbuf_reset@ --- *
  *
@@ -153,7 +158,6 @@ extern void dbuf_create(dbuf */*db*/);
  */
 
 extern void dbuf_reset(dbuf */*db*/);
-
 #define DBRESET(db) do {                                               \
   (db)->_b.p = (db)->_b.base; (db)->_b.limit = (db)->_b.base + (db)->sz; \
   (db)->_b.f = ((db)->_b.f&~BF_BROKEN) | BF_WRITE;                     \
@@ -169,6 +173,11 @@ extern void dbuf_reset(dbuf */*db*/);
  */
 
 extern void dbuf_destroy(dbuf */*db*/);
+#define DBDESTROY(db) do {                                             \
+  if ((db)->_b.base) x_free((db)->a, (db)->_b.base);                   \
+  (db)->_b.base = (db)->_b.p = (db)->_b.limit = 0;                     \
+  (db)->_b.f = BF_ALLOC | BF_WRITE; (db)->sz = 0;                      \
+} while (0)
 
 /* --- @{,d}buf_break@ --- *
  *
@@ -182,6 +191,8 @@ extern void dbuf_destroy(dbuf */*db*/);
 extern int buf_break(buf */*b*/);
 extern int dbuf_break(dbuf */*db*/);
 #define dbuf_break(db) (buf_break(DBUF_BUF(db)))
+#define BBREAK(b) do { (b)->f |= BF_BROKEN; } while (0)
+#define DBBREAK(db) BBREAK(DBUF_BUF(db))
 
 /* --- @{,d}buf_flip@ --- *
  *
@@ -601,7 +612,7 @@ BUF_DOSUFFIXES(BUF_DECL_PUTSTR_)
 
 /* --- @{,d}buf_getf64{,l,b} --- *
  *
- * Arguments:  @buf *b@ = pointer to a bfufer block
+ * Arguments:  @buf *b@ = pointer to a buffer block
  *             @double *x_out@ = where to put the result
  *
  * Returns:    Zero on success, @-1@ on failure (and the buffer is broken).
@@ -683,8 +694,8 @@ extern int dbuf_putf64b(dbuf */*db*/, double /*x*/);
   })                                                                   \
   MC_AFTER(tag##__poke, {                                              \
     size_t _delta = BLEN(b) - (mk) - (lensz);                          \
-    assert(check);                                                     \
-    if (BOK(b)) poke(BBASE(b) + (mk), _delta);                         \
+    if (!(check)) (b)->f |= BF_BROKEN;                                 \
+    else if (BOK(b)) poke(BBASE(b) + (mk), _delta);                    \
   })
 
 #define DBUF_ENCLOSETAG(tag, b, mk, check, poke, lensz)                        \
@@ -715,7 +726,7 @@ extern int dbuf_putf64b(dbuf */*db*/, double /*x*/);
 #define BUF_ENCLOSEITAG(tag, b, mk, W)                                 \
   BUF_ENCLOSETAG(tag, (b), (mk), (_delta <= MASK##W), STORE##W, SZ_##W)
 #define BUF_ENCLOSEKTAG(tag, b, mk, W)                         \
-  BUF_ENCLOSE(tag, (b), (mk), 1, BUF_STORESZK##W, 8)
+  BUF_ENCLOSETAG(tag, (b), (mk), 1, BUF_STORESZK##W, 8)
 #define BUF_ENCLOSEZTAG(tag, b)                                                \
   MC_AFTER(tag##__zero, { buf_putbyte((b), 0); })
 
@@ -743,43 +754,43 @@ extern int dbuf_putf64b(dbuf */*db*/, double /*x*/);
 
 #define BUF_ENCLOSE8(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 8)
 #define BUF_ENCLOSE16(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16)
-#define BUF_ENCLOSE16_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_B)
 #define BUF_ENCLOSE16_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_L)
+#define BUF_ENCLOSE16_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_B)
 #define BUF_ENCLOSE24(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24)
-#define BUF_ENCLOSE24_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_B)
 #define BUF_ENCLOSE24_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_L)
+#define BUF_ENCLOSE24_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_B)
 #define BUF_ENCLOSE32(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32)
-#define BUF_ENCLOSE32_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_B)
 #define BUF_ENCLOSE32_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_L)
+#define BUF_ENCLOSE32_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_B)
 #ifdef HAVE_UINT64
 #  define BUF_ENCLOSE64(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64)
-#  define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_B)
 #  define BUF_ENCLOSE64_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_L)
+#  define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_B)
 #else
 #  define BUF_ENCLOSE64(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64)
-#  define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_B)
 #  define BUF_ENCLOSE64_L(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_L)
+#  define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_B)
 #endif
 #define BUF_ENCLOSEZ(b) BUF_ENCLOSEZTAG(encl, (b))
 
 #define DBUF_ENCLOSE8(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 8)
 #define DBUF_ENCLOSE16(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16)
-#define DBUF_ENCLOSE16_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_B)
 #define DBUF_ENCLOSE16_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_L)
+#define DBUF_ENCLOSE16_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_B)
 #define DBUF_ENCLOSE24(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24)
-#define DBUF_ENCLOSE24_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_B)
 #define DBUF_ENCLOSE24_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_L)
+#define DBUF_ENCLOSE24_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_B)
 #define DBUF_ENCLOSE32(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32)
-#define DBUF_ENCLOSE32_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32_B)
 #define DBUF_ENCLOSE32_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32_L)
+#define DBUF_ENCLOSE32_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32_B)
 #ifdef HAVE_UINT64
 #  define DBUF_ENCLOSE64(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64)
-#  define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64_B)
 #  define DBUF_ENCLOSE64_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64_L)
+#  define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64_B)
 #else
 #  define DBUF_ENCLOSE64(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64)
-#  define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_B)
 #  define DBUF_ENCLOSE64_L(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_L)
+#  define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_B)
 #endif
 #define DBUF_ENCLOSEZ(db) DBUF_ENCLOSEZTAG(encl, (db))
 
index a48bedc47ebdde8b1ac1fe20c80836bff64835e5..6037c74c06b17720ab0a19e55cffec66db4bcfa8 100644 (file)
@@ -58,10 +58,11 @@ darray \- dense, dynamically resizing arrays
 .nf
 .B "#include <mLib/darray.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'size_t sz, len, off;"
-.B "\h'4n'unsigned push, unshift;"
-.B "\h'4n'arena *a;"
+.B "   size_t sz, len, off;"
+.B "   unsigned push, unshift;"
+.B "   arena *a;"
 .B "} da_base;"
 
 .B "#define DA_INIT ..."
index aa7e4a29ac933ffeca80ebd8af7b49691d65367a..0b6c2238b663e5e2349525b804168e6a05f6404d 100644 (file)
@@ -34,6 +34,7 @@
 #include "alloc.h"
 #include "arena.h"
 #include "darray.h"
+#include "growbuf.h"
 
 /*----- Magic numbers -----------------------------------------------------*/
 
@@ -111,8 +112,8 @@ void *da_ensure(da_base *b, void *v, size_t sz, size_t n)
    * two which is big enough, starting at double the current size.
    */
 
-  nsz = v ? b->sz + b->off : (DA_INITSZ >> 1);
-  do nsz <<= 1; while (nsz < rq);
+  nsz = b->sz + b->off;
+  GROWBUF_SIZE(nsz, rq, DA_INITSZ, sz);
 
   /* --- Reallocate the block --- *
    *
@@ -219,8 +220,8 @@ void *da_shunt(da_base *b, void *v, size_t sz, size_t n)
    * two which is big enough, starting at double the current size.
    */
 
-  nsz = v ? b->sz + b->off : (DA_INITSZ >> 1);
-  do nsz <<= 1; while (nsz < rq);
+  nsz = b->sz + b->off;
+  GROWBUF_SIZE(nsz, rq, DA_INITSZ, sz);
 
   /* --- Reallocate the block --- *
    *
index 48c2f6a9ad3facb4c1257bfde99aa8e5acd970d9..c1bde8e5883bbefff3a259644270a08cf7e894de 100644 (file)
@@ -51,9 +51,9 @@
  */
 
 static int putch(void *out, int ch)
-  { dstr *d = out; DPUTC(d, ch); return (0); }
+  { dstr *d = out; DPUTC(d, ch); return (1); }
 static int putm(void *out, const char *p, size_t sz)
-  { dstr *d = out; DPUTM(d, p, sz); return (0); }
+  { dstr *d = out; DPUTM(d, p, sz); return (sz); }
 
 static int nputf(void *out, size_t maxsz, const char *p, ...)
 {
index 7e154aee5cca1a791b53acecc3895648cc23284b..eee34cca0fea984bb1e581db0251a9255703a85f 100644 (file)
@@ -33,6 +33,7 @@
 
 #include "alloc.h"
 #include "dstr.h"
+#include "growbuf.h"
 
 /*----- Tunable constants -------------------------------------------------*/
 
@@ -91,29 +92,7 @@ void dstr_reset(dstr *d) { DRESET(d); }
  */
 
 void dstr_ensure(dstr *d, size_t sz)
-{
-  size_t rq = d->len + sz;
-  size_t nsz;
-
-  /* --- If we have enough space, just leave it --- */
-
-  if (rq <= d->sz)
-    return;
-
-  /* --- Grow the buffer --- */
-
-  nsz = d->sz;
-
-  if (nsz == 0)
-    nsz = (DSTR_INITSZ >> 1);
-  do nsz <<= 1; while (nsz < rq);
-
-  if (d->buf)
-    d->buf = x_realloc(d->a, d->buf, nsz, d->sz);
-  else
-    d->buf = x_alloc(d->a, nsz);
-  d->sz = nsz;
-}
+  { GROWBUF_EXTEND(d->a, d->buf, d->sz, d->len + sz, DSTR_INITSZ, 1); }
 
 /* --- @dstr_putc@ --- *
  *
index 19522ee76cec02af13bcdf4ffa03890e2cbda38d..c06727516ed58322867dc12cc7789d471b95696d 100644 (file)
@@ -38,15 +38,16 @@ hash \- low-level hashtable implementation
 .nf
 .B "#include <mLib/hash.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'uint32 mask;"
-.B "\h'4n'hash_base **v;"
-.B "\h'4n'arena *a;"
+.B "   uint32 mask;"
+.B "   hash_base **v;"
+.B "   arena *a;"
 .B "} hash_table;"
 
 .B "typedef struct {"
-.B "\h'4n'hash_base *next;"
-.B "\h'4n'uint32 hash;"
+.B "   hash_base *next;"
+.B "   uint32 hash;"
 .B "} hash_base;"
 
 .B "typedef struct { ...\& } hash_iter;"
index 045ea54d7f92f6563c13e64b56bfa343564020e3..a2afcaf87285d6167344d370cc8c9ca3c5cd59e8 100644 (file)
@@ -36,10 +36,10 @@ sym \- symbol table manager
 .BI "void sym_create(sym_table *" t );
 .BI "void sym_destroy(sym_table *" t );
 
-.ds mT \fBvoid *sym_find(
-.BI "\*(mTsym_table *" t ,
-.BI "\h'\w'\*(mT'u'const char *" n ", long " l ,
-.BI "\h'\w'\*(mT'u'size_t " sz ", unsigned *" f );
+.ta \w'\fBvoid *sym_find('u
+.BI "void *sym_find(sym_table *" t ,
+.BI "  const char *" n ", long " l ,
+.BI "  size_t " sz ", unsigned *" f );
 .BI "void sym_remove(sym_table *" t ", void *" b );
 
 .BI "const char *SYM_NAME(const void *" p );
index 3fd51fc2c73d81612d7906275bdcf64dc4b12b2c..2ed2fa9470258457f0eae69e6048ad92dcf04cc2 100644 (file)
@@ -19,10 +19,10 @@ fdflags \- set file and file descriptor flags
 .nf
 .B "#include <mLib/fdflags.h>"
 
-.ds mT \fBint fdflags(
-.BI "\*(mTint " fd ,
-.BI "\h'\w'\*(mT'u'unsigned " fbic ", unsigned " fxor ,
-.BI "\h'\w'\*(mT'u'unsigned " fdbic ", unsigned " fdxor );
+.ta \w'\fBint fdflags('u
+.BI "int fdflags(int " fd ,
+.BI "  unsigned " fbic ", unsigned " fxor ,
+.BI "  unsigned " fdbic ", unsigned " fdxor );
 .fi
 .SH "DESCRIPTION"
 .B fdflags
index 2d80fada3adb8006aacc720fd0e3ef8d39d7dc33..d7b18e5cce91dd405ed4179bb7e373cd10419597 100644 (file)
@@ -7,10 +7,11 @@ lock \- oversimplified file locking interface
 .nf
 .B "#include <mLib/lock.h>"
 
+.ta 2n
 .B "enum {"
-.B "\h'4n'LOCK_UNLOCK = ...,"
-.B "\h'4n'LOCK_EXCL = ...,"
-.B "\h'4n'LOCK_NONEXCL = ..."
+.B "   LOCK_UNLOCK = ...,"
+.B "   LOCK_EXCL = ...,"
+.B "   LOCK_NONEXCL = ..."
 .B "};"
 
 .BI "int lock_file(int " fd ", unsigned " how );
index 1e1b8365e7d265e9a7e4e25c9ffd8e81427117e1..f08c13e7171b599d0b62c99a3f94240cba05c2ed 100644 (file)
@@ -26,9 +26,10 @@ mdup \- renumber file descriptors
 .nf
 .B "#include <mLib/mdup.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4'int cur;"
-.B "\h'4n'int want;"
+.B "   int cur;"
+.B "   int want;"
 .B "} mdup_fd;"
 
 .BI "int mdup(mdup_fd *" v ", size_t " n ");"
@@ -116,13 +117,14 @@ int i;
 if (pipe(p_in) || pipe(p_out) || pipe(p_err)) goto error;
 if ((kid = fork()) < 0) goto error;
 if (!kid) {
-  if (dup2(p_in[0], STDIN_FILENO) < 0 ||
-      dup2(p_out[1], STDOUT_FILENO) < 0 ||
-      dup2(p_err[2], STDERR_FILENO) < 0 ||
-      close(p_in[0]) || close(p_out[0]) || close(p_err[0]) ||
-      close(p_in[1]) || close(p_out[1]) || close(p_err[1]))
-    _exit(127);
-  execvp("/bin/sh", "sh", "-c", "...", (char *)0);
+.ta 2n 4n 2n+\w'\fBif ('u
+       if (dup2(p_in[0], STDIN_FILENO) < 0 ||
+                       dup2(p_out[1], STDOUT_FILENO) < 0 ||
+                       dup2(p_err[2], STDERR_FILENO) < 0 ||
+                       close(p_in[0]) || close(p_out[0]) || close(p_err[0]) ||
+                       close(p_in[1]) || close(p_out[1]) || close(p_err[1]))
+               _exit(127);
+       execvp("/bin/sh", "sh", "-c", "...", (char *)0);
 }
 \&...
 .VE
@@ -145,6 +147,7 @@ the child.
 Here's how to rewrite the above function using
 .BR mdup .
 .VS
+.ta 2n 4n 2n+\w'\fBmd[0].cur = p_out[1]; 'u
 #define P_INIT { \-1, \-1 }
 int p_in[2] = P_INIT, p_out[2] = P_INIT, p_err[2] = P_INIT;
 pid_t kid = -1;
@@ -154,13 +157,13 @@ int i;
 if (pipe(p_in) || pipe(p_out) || pipe(p_err)) goto error;
 if ((kid = fork()) < 0) goto error;
 if (!kid) {
-  if (close(p_in[1] || close(p_out[0]) || close(p_err[0]))
-    goto _exit(127);
-  md[0].cur = p_in[0];  md[0].want = STDIN_FILENO;
-  md[1].cur = p_out[1]; md[1].want = STDOUT_FILENO;
-  md[2].cur = p_err[1]; md[2].want = STDERR_FILENO;
-  if (mdup(md, 3)) _exit(127);
-  execvp("/bin/sh", "sh", "-c", "...", (char *)0);
+       if (close(p_in[1] || close(p_out[0]) || close(p_err[0]))
+               goto _exit(127);
+       md[0].cur = p_in[0];            md[0].want = STDIN_FILENO;
+       md[1].cur = p_out[1];           md[1].want = STDOUT_FILENO;
+       md[2].cur = p_err[1];           md[2].want = STDERR_FILENO;
+       if (mdup(md, 3)) _exit(127);
+       execvp("/bin/sh", "sh", "-c", "...", (char *)0);
 }
 \&...
 .VE
index 0d93bc1785d685229b1ae9b4079581f35a8e2694..b6ba60578cd8b63468d0c8337096014fd7b23a07 100644 (file)
@@ -239,6 +239,10 @@ static void test_copy_bytes
   memcpy(out->v.bytes.p, in->v.bytes.p, in->v.bytes.sz);
 }
 
+static void test_copy_buffer
+  (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+  { tvec_initbuffer(&out->v, &in->v, in->v.buf.sz); }
+
 #define test_copy_int test_copy_simple
 #define test_copy_uint test_copy_simple
 #define test_copy_ienum test_copy_simple
@@ -249,7 +253,6 @@ static void test_copy_bytes
 #define test_copy_flags test_copy_simple
 #define test_copy_float test_copy_simple
 #define test_copy_fltish test_copy_simple
-#define test_copy_buffer test_copy_bytes
 
 #define COPYREG(name, i, ty, argslot, argval)                          \
        static DSGINIT(const) struct tvec_regdef name##_copyregs[] = {  \
@@ -493,7 +496,7 @@ static const struct tvec_remotefork sleep_testenv =
   { TVEC_REMOTEFORK(&sleep_subenv._env, 0) };
 
 static const struct tvec_regdef sleep_regs[] = {
-  { "time",    RV,     &tvty_float,    0,              { &tvflt_nonneg } },
+  { "time",    RV,     &tvty_duration, 0,              { &tvflt_nonneg } },
   { "z",       RVOUT,  &tvty_float,    0,              { &tvflt_nonneg } },
   TVEC_ENDREGS
 };
index 07e1f48dfe12d732e75689dfa3fa99cc212d8bdc..b6e30f305d0d4cad5e2e48c6e00a3593966f2ec7 100644 (file)
@@ -21,24 +21,25 @@ testrig \- generic test rig
 
 .B "#define TEST_FIELDMAX ..."
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'unsigned tests, failed;"
+.B "   unsigned tests, failed;"
 .B "} test_results";
 
 .B "typedef struct {"
-.BI "\h'4n'void (*cvt)(const char *" buf ", dstr *" d );
-.BI "\h'4n'void (*dump)(dstr *" d ", FILE *" fp );
+.BI "  void (*cvt)(const char *" buf ", dstr *" d );
+.BI "  void (*dump)(dstr *" d ", FILE *" fp );
 .B "} test_type";
 
 .B "typedef struct {"
-.B "\h'4n'const char *name;"
-.BI "\h'4n'void (*test)(dstr " dv "[]);"
-.B "\h'4n'const test_type *f[TEST_FIELDMAX];"
+.B "   const char *name;"
+.BI "  void (*test)(dstr " dv "[]);"
+.B "   const test_type *f[TEST_FIELDMAX];"
 .B "} test_chunk";
 
 .B "typedef struct {"
-.B "\h'4n'const char *name;"
-.B "\h'4n'const test_chunk *chunks;"
+.B "   const char *name;"
+.B "   const test_chunk *chunks;"
 .B "} test_suite";
 
 .B "const test_type type_hex;"
@@ -48,14 +49,12 @@ testrig \- generic test rig
 .B "const test_type type_ulong;"
 .B "const test_type type_uint32;"
 
-.ds mT \fBint test_do(
-.BI "\*(mTconst test_suite " suite [],
-.BI "\h'\w'\*(mT'u'FILE *" fp ,
-.BI "\h'\w'\*(mT'u'test_results *" results );
-.ds mT \fBvoid test_run(
-.BI "\*(mTint " argc ", char *" argv [],
-.BI "\h'\w'\*(mT'u'const test_chunk " chunk [],
-.BI "\h'\w'\*(mT'u'const char *" def );
+.ta \w'\fBint test_do('u
+.BI "int test_do(const test_suite " suite [],
+.BI "  FILE *" fp ", test_results *" results );
+.ta \w'\fBvoid test_run('u
+.BI "void test_run(int " argc ", char *" argv [],
+.BI "  const test_chunk " chunk "[], const char *" def );
 .fi
 .SH DESCRIPTION
 .SS Structure
index 456729dc2c032dbbb2abecb1c118edc147557c14..22e200a964eb550b7d6d1e50236b158c5e68b20a 100644 (file)
@@ -362,30 +362,33 @@ AT_CLEANUP
 ###--------------------------------------------------------------------------
 AT_SETUP([tvec type-buffer])
 
-test_parse([buffer], [16], [16 B])
-test_parse([buffer], [16;?], [16 B])
-test_parse([buffer], [16 ;?], [16 B])
-test_parse([buffer], [16384], [16 kB])
-test_parse([buffer], [16777216], [16 MB])
-test_parse([buffer], [16k], [16 kB])
-test_parse([buffer], [16k;?], [16 kB])
-test_parse([buffer], [16k ;?], [16 kB])
-test_parse([buffer], [16 k], [16 kB])
-test_parse([buffer], [16 k;?], [16 kB])
-test_parse([buffer], [16 k ;?], [16 kB])
-test_parse([buffer], [16kB], [16 kB])
-test_parse([buffer], [16kB;?], [16 kB])
-test_parse([buffer], [16kB ;?], [16 kB])
-test_parse([buffer], [16 kB], [16 kB])
-test_parse([buffer], [16 kB;?], [16 kB])
-test_parse([buffer], [16 kB ;?], [16 kB])
+test_parse([buffer], [16], [16 B ; = 16 = 0x10])
+test_parse([buffer], [16;?], [16 B ; = 16 = 0x10])
+test_parse([buffer], [16 ;?], [16 B ; = 16 = 0x10])
+test_parse([buffer], [16384], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16777216], [16 MB ; = 16777216 = 0x01000000])
+test_parse([buffer], [16k], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16k;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16k ;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16 k], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16 k;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16 k ;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16kB], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16kB;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16kB ;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16 kB], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16 kB;?], [16 kB ; = 16384 = 0x4000])
+test_parse([buffer], [16 kB ;?], [16 kB ; = 16384 = 0x4000])
+
+test_parse([buffer], [16777216@4096+17],
+       [16 MB @ 4 kB + 17 B ; = 16777216 @ 4096 + 17 = 0x01000000 @ 0x1000 + 0x11])
 
 test_parserr([buffer], [16!], [3], [invalid buffer length `16!'])
 test_parserr([buffer], [16   !], [3], [invalid buffer length `16 !'])
 test_parserr([buffer], [16 k!], [3], [invalid buffer length `16 k!'])
 test_parserr([buffer], [16 kB!], [3], [invalid buffer length `16 kB!'])
 test_parserr([buffer], [16 kB !],
-       [3], [syntax error: expected end-of-line but found `!'])
+       [3], [syntax error: expected `@' but found `!'])
 test_parserr([buffer], [16 EB], [3], [buffer length `16 EB' out of range])
 
 AT_CLEANUP
index 51b46d20eab44acfd976ac884e9f7a77daa6f76d..6a98f7368f02936307cb9139425837e86ca1ca70 100644 (file)
@@ -33,6 +33,7 @@
 #include <string.h>
 
 #include "alloc.h"
+#include "growbuf.h"
 #include "tvec.h"
 
 /*----- Output ------------------------------------------------------------*/
@@ -469,6 +470,7 @@ int tvec_nexttoken(struct tvec_state *tv)
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @dstr *d@ = string to append the word to
+ *             @const char **p_inout@ = pointer into string, updated
  *             @const char *delims@ = additional delimiters to stop at
  *             @const char *expect@, @va_list ap@ = what was expected
  *
@@ -491,38 +493,54 @@ int tvec_nexttoken(struct tvec_state *tv)
  *             word constituents, a null terminator is written to @d@, and
  *             it is safe to treat the string in @d@ as being null-
  *             terminated.
+ *
+ *             If @p_inout@ is not null, then @*p_inout@ must be a pointer
+ *             into @d->buf@, which will be adjusted so that it will
+ *             continue to point at the same position even if the buffer is
+ *             reallocated.  As a subtle tweak, if @*p_inout@ initially
+ *             points at the end of the buffer, then it will be adjusted to
+ *             point at the beginning of the next word, rather than at the
+ *             additional intervening space.
  */
 
-int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
-                 const char *expect, ...)
+int tvec_readword(struct tvec_state *tv, dstr *d, const char **p_inout,
+                 const char *delims, const char *expect, ...)
 {
   va_list ap;
   int rc;
 
   va_start(ap, expect);
-  rc = tvec_readword_v(tv, d, delims, expect, &ap);
+  rc = tvec_readword_v(tv, d, p_inout, delims, expect, &ap);
   va_end(ap);
   return (rc);
 }
 
-int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
-                   const char *expect, va_list *ap)
+int tvec_readword_v(struct tvec_state *tv, dstr *d, const char **p_inout,
+                   const char *delims, const char *expect, va_list *ap)
 {
+  size_t pos = 0;
   int ch;
 
+  tvec_skipspc(tv);
+
   ch = getc(tv->fp);
   if (!ch || ch == '\n' || ch == EOF || ch == ';' ||
       (delims && strchr(delims, ch))) {
     if (expect) return (tvec_syntax(tv, ch, expect, ap));
     else { ungetc(ch, tv->fp); return (-1); }
   }
-  if (d->len) DPUTC(d, ' ');
+  if (p_inout) pos = *p_inout - d->buf;
+  if (d->len) {
+    if (pos == d->len) pos++;
+    DPUTC(d, ' ');
+  }
   do {
     DPUTC(d, ch);
     ch = getc(tv->fp);
   } while (ch && ch != EOF && !isspace(ch) &&
           (!delims || !strchr(delims, ch)));
   DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
+  if (p_inout) *p_inout = d->buf + pos;
   return (0);
 }
 
@@ -1000,8 +1018,7 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
        /* Read the group name.  There may be leading and trailing
         * whitespace.
         */
-       tvec_skipspc(tv);
-       DRESET(&d); tvec_readword(tv, &d, "];", "group name");
+       DRESET(&d); tvec_readword(tv, &d, 0, "];", "group name");
        tvec_skipspc(tv);
        ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
 
@@ -1062,7 +1079,8 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
           */
          ungetc(ch, tv->fp);
          DRESET(&d);
-         if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line;
+         if (tvec_readword(tv, &d, 0, "=:;", "register name"))
+           goto flush_line;
 
          /* Now there should be a separator. */
          tvec_skipspc(tv); ch = getc(tv->fp);
@@ -1103,12 +1121,8 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
            if (vd->regsz <= sizeof(rbuf))
              r = &rbuf;
            else {
-             if (rsz < vd->regsz) {
-               xfree(r_alloc);
-               if (!rsz) rsz = 8*sizeof(void *);
-               while (rsz < vd->regsz) rsz *= 2;
-               r_alloc = xmalloc(rsz);
-             }
+             GROWBUF_REPLACE(&arena_stdlib, r_alloc, rsz, vd->regsz,
+                             8*sizeof(void *), 1);
              r = r_alloc;
            }
 
index 8c9c2141ff5a827129cfb1d33b6097f73b3e0dad..cacd94cdbd24d50c5a25938a4c9afe8da96d0a00 100644 (file)
@@ -673,7 +673,8 @@ static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
   va_start(ap, p);
   n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
   va_end(ap);
-  return (layout_string(&h->lyt, h->outbuf, n));
+  if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
+  return (n);
 }
 
 static const struct gprintf_ops human_printops =
@@ -1273,10 +1274,20 @@ struct tap_output {
  */
 
 static int tap_writech(void *go, int ch)
-  { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
+{
+  struct tap_output *t = go;
+
+  if (layout_char(&t->lyt, ch)) return (-1);
+  else return (1);
+}
 
 static int tap_writem(void *go, const char *p, size_t sz)
-  { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
+{
+  struct tap_output *t = go;
+
+  if (layout_string(&t->lyt, p, sz)) return (-1);
+  else return (sz);
+}
 
 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
 {
@@ -1287,7 +1298,8 @@ static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
   va_start(ap, p);
   n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
   va_end(ap);
-  return (layout_string(&t->lyt, t->outbuf, n));
+  if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
+  return (n);
 }
 
 static const struct gprintf_ops tap_printops =
index b87351b486eb521a6e2e714bd087066cd4383539..1a3bd0ed3437c635a4ead77515d23e41d955be7c 100644 (file)
@@ -44,6 +44,7 @@
 #include "buf.h"
 #include "compiler.h"
 #include "fdflags.h"
+#include "growbuf.h"
 #include "lbuf.h"
 #include "mdup.h"
 #include "quis.h"
@@ -81,7 +82,7 @@
 
 static void init_comms(struct tvec_remotecomms *rc)
 {
-  rc->bin = 0; rc->binsz = 0; dbuf_create(&rc->bout);
+  rc->bin = 0; rc->binsz = 0; DBCREATE(&rc->bout);
   rc->infd = rc->outfd = -1; rc->f = 0;
 }
 
@@ -120,7 +121,7 @@ static void close_comms(struct tvec_remotecomms *rc)
  */
 
 static void release_comms(struct tvec_remotecomms *rc)
-  { close_comms(rc); xfree(rc->bin); dbuf_destroy(&rc->bout); }
+  { close_comms(rc); xfree(rc->bin); DBDESTROY(&rc->bout); }
 
 /* --- @setup_comms@ --- *
  *
@@ -378,13 +379,7 @@ static int receive_buffered(struct tvec_state *tv,
   if (rc->binlen - rc->binoff >= want) return (RECV_OK);
 
   /* If the buffer is too small then we must grow it. */
-  if (want > rc->binsz) {
-    sz = rc->binsz; if (!sz) sz = RECVBUFSZ;
-    while (sz < want) { assert(sz < (size_t)-1/2); sz *= 2; }
-    if (!rc->bin) rc->bin = xmalloc(sz);
-    else rc->bin = xrealloc(rc->bin, sz, rc->binsz);
-    rc->binsz = sz;
-  }
+  GROWBUF_EXTEND(&arena_stdlib, rc->bin, rc->binsz, want, RECVBUFSZ, 1);
 
   /* Shunt the unused existing material to the start of the buffer. */
   memmove(rc->bin, rc->bin + rc->binoff, rc->binlen - rc->binoff);
@@ -712,12 +707,8 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
              if (vd->regsz <= sizeof(rbuf))
                r = &rbuf;
              else {
-               if (rsz < vd->regsz) {
-                 xfree(r_alloc);
-                 if (!rsz) rsz = 8*sizeof(void *);
-                 while (rsz < vd->regsz) rsz *= 2;
-                 r_alloc = xmalloc(rsz);
-               }
+               GROWBUF_REPLACE(&arena_stdlib, r_alloc, rsz, vd->regsz,
+                               8*sizeof(void *), 1);
                r = r_alloc;
              }
 
index e76d4a185108ee07d1e47dfc4d057a9921ee54bb..068d5654aac6fa4e5111dd4d1f013591744248e3 100644 (file)
@@ -107,7 +107,7 @@ static int unsigned_from_buf(buf *b, unsigned long *u_out)
 
   ASSIGN64(ulmax, ULONG_MAX);
   if (buf_getk64l(b, &k)) return (-1);
-  if (CMP64(k, >, ulmax)) return (-1);
+  if (CMP64(k, >, ulmax)) { buf_break(b); return (-1); }
   *u_out = GET64(unsigned long, k); return (0);
 }
 
@@ -161,7 +161,7 @@ static int signed_from_buf(buf *b, long *i_out)
   else {
     CPL64(k, k);
     if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
-    else return (-1);
+    else { buf_break(b); return (-1); }
   }
   return (0);
 }
@@ -387,6 +387,90 @@ static int parse_signed(long *i_out, const char *p,
   if (check_signed_range(i, ir, tv)) return (-1);
   *i_out = i; return (0);
 }
+static const char size_units[] = "kMGTPEZY";
+
+/* --- @parse_size@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @size_t *u_out@ = where to put the answer
+ *             @const char *delims@ = delimiters
+ *             @const char *what@ = description of what we're parsing
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Parse a memory size.
+ */
+
+static int parse_size(struct tvec_state *tv, size_t *u_out,
+                     const char *delims, const char *what)
+{
+  dstr d = DSTR_INIT;
+  const char *p, *unit;
+  unsigned long u, t;
+  int rc;
+  unsigned f = 0;
+#define f_range 1u
+
+  if (tvec_readword(tv, &d, 0, delims, what)) { rc = -1; goto end; }
+  p = d.buf;
+  if (parse_unsigned_integer(&u, &p, p)) goto bad;
+  if (!*p) tvec_readword(tv, &d, &p, delims, 0);
+
+  if (u > (size_t)-1) goto rangerr;
+  for (t = u, unit = size_units; *unit; unit++) {
+    if (t > (size_t)-1/1024) f |= f_range;
+    else t *= 1024;
+    if (*p == *unit) {
+      if (f&f_range) goto rangerr;
+      u = t; p++; break;
+    }
+  }
+  if (*p == 'B') p++;
+  if (*p) goto bad;
+
+  *u_out = u; rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+
+bad:
+  tvec_error(tv, "invalid %s `%s'", what, d.buf);
+  rc = -1; goto end;
+
+rangerr:
+  tvec_error(tv, "%s `%s' out of range", what, d.buf);
+  rc = -1; goto end;
+
+#undef f_range
+}
+
+/* --- @format_size@ --- *
+ *
+ * Arguments:  @const struct gprintf_ops *gops@ = print operations
+ *             @void *go@ = print destination
+ *             @unsigned long u@ = a size
+ *             @unsigned style@ = style (@TVSF_...@)
+ *
+ * Returns:    ---
+ *
+ * Use:                Format @u@ as a size in bytes to the destination, expressing
+ *             it with a unit prefix if this is possible exactly.
+ */
+
+static void format_size(const struct gprintf_ops *gops, void *go,
+                       unsigned long u, unsigned style)
+{
+  const char *unit;
+
+  if (!u || u%1024)
+    gprintf(gops, go, "%lu%sB", u, style&TVSF_COMPACT ? "" : " ");
+  else {
+    for (unit = size_units, u /= 1024;
+        !(u%1024) && unit[1];
+        u /= 1024, unit++);
+    gprintf(gops, go, "%lu%s%cB", u, style&TVSF_COMPACT ? "" : " ", *unit);
+  }
+}
 
 /*----- Floating-point utilities ------------------------------------------*/
 
@@ -534,8 +618,8 @@ static void format_floating(const struct gprintf_ops *gops, void *go,
  *
  * Use:                Parse a floating-point number from a string.  Reports any
  *             necessary errors.  If @q_out@ is not null then trailing
- *             material is permitted and a pointer to it is left in
- *             @*q_out@; this will be null if there is no trailing material.
+ *             material is permitted and a pointer to it (or the end of the
+ *             string) is left in @*q_out@.
  */
 
 static int parse_floating(double *x_out, const char **q_out, const char *p,
@@ -547,11 +631,10 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
   double x;
   int olderr, rc;
 
-  if (q_out) *q_out = 0;
-
   /* Check for special tokens. */
   if (STRCMP(p, ==, "#nan")) {
 #ifdef NAN
+    if (q_out) *q_out = p + strlen(p);
     x = NAN; rc = 0;
 #else
     tvec_error(tv, "NaN not supported on this system");
@@ -562,6 +645,7 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
   else if (STRCMP(p, ==, "#inf") ||
           STRCMP(p, ==, "#+inf") || STRCMP(p, ==, "+#inf")) {
 #ifdef INFINITY
+    if (q_out) *q_out = p + strlen(p);
     x = INFINITY; rc = 0;
 #else
     tvec_error(tv, "infinity not supported on this system");
@@ -571,6 +655,7 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
 
   else if (STRCMP(p, ==, "#-inf") || STRCMP(p, ==, "-#inf")) {
 #ifdef INFINITY
+    if (q_out) *q_out = p + strlen(p);
     x = -INFINITY; rc = 0;
 #else
     tvec_error(tv, "infinity not supported on this system");
@@ -593,9 +678,8 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
     /* Parse the number using the system parser. */
     olderr = errno; errno = 0;
     x = strtod(p, &q);
-    if (!*q) /* nothing to do */;
-    else if (q_out) *q_out = q;
-    else { tvec_syntax(tv, *q, "end-of-line"); rc = -1; goto end; }
+    if (q_out) *q_out = q;
+    else if (*q) { tvec_syntax(tv, *q, "end-of-line"); rc = -1; goto end; }
     if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
       tvec_error(tv, "invalid floating-point number `%.*s': %s",
                 (int)(q - p), p, strerror(errno));
@@ -1135,7 +1219,7 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
        ungetc(ch, tv->fp);
        if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
        cdc = 0;
-       DRESET(&w); tvec_readword(tv, &w, ";", "character name");
+       DRESET(&w); tvec_readword(tv, &w, 0, ";", "character name");
        if (read_charname(&ch, w.buf, RCF_EOFOK)) {
          rc = tvec_error(tv, "unknown character name `%s'", d.buf);
          goto end;
@@ -1148,7 +1232,7 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
        if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
        cdc = 0;
        ungetc(ch, tv->fp);
-       DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
+       DRESET(&w); tvec_readword(tv, &w, 0, ";", "`!'-keyword");
 
        /* Change bareword coding system. */
        if (STRCMP(w.buf, ==, "!bare"))
@@ -1167,7 +1251,7 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
            goto end;
          }
          DRESET(&w);
-         if (tvec_readword(tv, &w, ";{", "repeat count"))
+         if (tvec_readword(tv, &w, 0, ";{", "repeat count"))
            { rc = -1; goto end;  }
          if (parse_unsigned_integer(&n, &q, w.buf)) {
            rc = tvec_error(tv, "invalid repeat count `%s'", w.buf);
@@ -1213,7 +1297,8 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
          default:
            assert(ccl);
            ungetc(ch, tv->fp); DRESET(&w);
-           if (tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name))
+           if (tvec_readword(tv, &w, 0, ";",
+                             "%s-encoded fragment", ccl->name))
              { rc = -1; goto end; }
            if (!cdc) cdc = ccl->decoder(cdf);
            err = cdc->ops->code(cdc, w.buf, w.len, &d);
@@ -1370,7 +1455,8 @@ static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, 0, ";", "signed integer"))
+    { rc = -1; goto end; }
   if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
@@ -1385,7 +1471,8 @@ static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, 0, ";", "unsigned integer"))
+    { rc = -1; goto end; }
   if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
@@ -1612,7 +1699,7 @@ static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "floating-point number"))
+  if (tvec_readword(tv, &d, 0, ";", "floating-point number"))
     { rc = -1; goto end; }
   if (parse_floating(&rv->f, 0, d.buf, rd->arg.p, tv))
     { rc = -1; goto end; }
@@ -1806,21 +1893,17 @@ static int parse_duration(union tvec_regval *rv,
 {
   const struct duration_unit *u;
   const char *q;
-  dstr d = DSTR_INIT; size_t pos;
+  dstr d = DSTR_INIT;
   double t;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "duration")) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, 0, ";", "duration")) { rc = -1; goto end; }
   if (parse_floating(&t, &q, d.buf,
                     rd->arg.p ? rd->arg.p : &tvflt_nonneg, tv))
     { rc = -1; goto end; }
 
-  if (!q) {
-    tvec_skipspc(tv); pos = d.len;
-    if (!tvec_readword(tv, &d, ";", 0)) q = d.buf + pos + 1;
-  }
-
-  if (q) {
+  if (!*q) tvec_readword(tv, &d, &q, ";", 0);
+  if (*q) {
     for (u = duration_units; u->unit; u++)
       if (STRCMP(q, ==, u->unit)) { t *= u->scale; goto found_unit; }
     rc = tvec_syntax(tv, *q, "end-of-line"); goto end;
@@ -2001,7 +2084,7 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
   if (signed_from_buf(b, &i)) return (-1);
   if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p;
   else if (i == -1) rv->p = 0;
-  else return (-1);
+  else { buf_break(b); return (-1); }
   return (0);
 }
 
@@ -2032,7 +2115,7 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
     dstr d = DSTR_INIT;                                                        \
     int rc;                                                            \
                                                                        \
-    if (tvec_readword(tv, &d, ";", "enumeration tag or " LITSTR_##tag_)) \
+    if (tvec_readword(tv, &d, 0, ";", "enumeration tag or " LITSTR_##tag_)) \
       { rc = -1; goto end; }                                           \
     for (a = ei->av; a->tag; a++)                                      \
       if (STRCMP(a->tag, ==, d.buf)) { FOUND_##tag_ goto done; }       \
@@ -2293,7 +2376,7 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
 
     /* Read the next item. */
     DRESET(&d);
-    if (tvec_readword(tv, &d, "|;", "flag name or integer"))
+    if (tvec_readword(tv, &d, 0, "|;", "flag name or integer"))
       { rc = -1; goto end; }
 
     /* Try to find a matching entry in the table. */
@@ -2436,7 +2519,7 @@ static int tobuf_char(buf *b, const union tvec_regval *rv,
 
   if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
   else if (rv->i == EOF) u = MASK32;
-  else return (-1);
+  else { buf_break(b); return (-1); }
   return (buf_putu32l(b, u));
 }
 
@@ -2462,7 +2545,7 @@ static int frombuf_char(buf *b, union tvec_regval *rv,
   if (buf_getu32l(b, &u)) return (-1);
   if (0 <= u && u <= UCHAR_MAX) rv->i = u;
   else if (u == MASK32) rv->i = EOF;
-  else return (-1);
+  else { buf_break(b); return (-1); }
   return (0);
 }
 
@@ -2553,7 +2636,8 @@ static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
      */
 
     ungetc(ch, tv->fp);
-    if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
+    if (tvec_readword(tv, &d, 0, ";", "character name"))
+      { rc = -1; goto end; }
     if (STRCMP(d.buf, !=, "#")) {
       if (read_charname(&ch, d.buf, RCF_EOFOK)) {
        rc = tvec_error(tv, "unknown character name `%s'", d.buf);
@@ -3182,7 +3266,37 @@ void tvec_allocbytes(union tvec_regval *rv, size_t sz)
 
 /*----- Buffer type -------------------------------------------------------*/
 
-/* Buffers are initialized and released as binary strings. */
+/* --- @init_buffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a register value.
+ *
+ *             Buffer values values are initialized with a null pointer,
+ *             zero length, and zero residue, modulus, and offset.
+ */
+
+static void init_buffer(union tvec_regval *rv, const struct tvec_regdef *rd)
+  { rv->buf.p = 0; rv->buf.sz = rv->buf.a = rv->buf.m = rv->buf.off = 0; }
+
+/* --- @release_buffer@, @release_bytes@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Release resources held by a register value.
+ *
+ *             Buffers are freed.
+ */
+
+static void release_buffer(union tvec_regval *rv,
+                          const struct tvec_regdef *rd)
+  { if (rv->buf.p) xfree(rv->buf.p - rv->buf.off); }
 
 /* --- @eq_buffer@ --- *
  *
@@ -3193,14 +3307,19 @@ void tvec_allocbytes(union tvec_regval *rv, size_t sz)
  *
  * Use:                Compare register values for equality.
  *
- *             Buffer values are equal if and only if their sizes are equal;
- *             their contents are %%\emph{not}%% compared.
+ *             Buffer values are equal if and only if their sizes and
+ *             alignment parameters are equal; their contents are
+ *             %%\emph{not}%% compared.
  */
 
 static int eq_buffer(const union tvec_regval *rv0,
                     const union tvec_regval *rv1,
                     const struct tvec_regdef *rd)
-  { return (rv0->bytes.sz == rv1->bytes.sz); }
+{
+  return (rv0->buf.sz == rv1->buf.sz &&
+         rv0->buf.a == rv1->buf.a &&
+         rv0->buf.m == rv1->buf.m);
+}
 
 /* --- @tobuf_buffer@ --- *
  *
@@ -3212,27 +3331,17 @@ static int eq_buffer(const union tvec_regval *rv0,
  *
  * Use:                Serialize a register value to a buffer.
  *
- *             Buffer values are serialized as just their lengths, as
- *             unsigned integers.
+ *             Buffer values are serialized as their lengths, residues, and
+ *             moduli, as unsigned integers.
  */
 
 static int tobuf_buffer(buf *b, const union tvec_regval *rv,
                         const struct tvec_regdef *rd)
-  { return (unsigned_to_buf(b, rv->bytes.sz)); }
-
-/* --- @allocate_buffer@ --- *
- *
- * Arguments:  @union tvec_regval *rv@ = register value
- *             @size_t sz@ = size to allocate
- *
- * Returns:    ---
- *
- * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
- *             distinctive pattern.
- */
-
-static void allocate_buffer(union tvec_regval *rv, size_t sz)
-  { tvec_allocbytes(rv, sz); memset(rv->bytes.p, '?', sz); }
+{
+  return (unsigned_to_buf(b, rv->buf.sz) ||
+         unsigned_to_buf(b, rv->buf.a) ||
+         unsigned_to_buf(b, rv->buf.m));
+}
 
 /* --- @frombuf_buffer@ --- *
  *
@@ -3252,11 +3361,14 @@ static void allocate_buffer(union tvec_regval *rv, size_t sz)
 static int frombuf_buffer(buf *b, union tvec_regval *rv,
                          const struct tvec_regdef *rd)
 {
-  unsigned long u;
+  unsigned long sz, a, m;
 
-  if (unsigned_from_buf(b, &u)) return (-1);
-  if (u > (size_t)-1) return (-1);
-  allocate_buffer(rv, u);
+  if (unsigned_from_buf(b, &sz)) return (-1);
+  if (unsigned_from_buf(b, &a)) return (-1);
+  if (unsigned_from_buf(b, &m)) return (-1);
+  if (sz > (size_t)-1 || a > (size_t)-1 || m > (size_t)-1)
+    { buf_break(b); return (-1); }
+  rv->buf.sz = sz; rv->buf.a = a; rv->buf.m = m;
   return (0);
 }
 
@@ -3279,56 +3391,42 @@ static int frombuf_buffer(buf *b, union tvec_regval *rv,
  *             pattern.
  */
 
-static const char units[] = "kMGTPEZY";
-
 static int parse_buffer(union tvec_regval *rv,
                        const struct tvec_regdef *rd,
                        struct tvec_state *tv)
 {
-  dstr d = DSTR_INIT;
-  const char *q, *unit;
-  size_t pos;
-  unsigned long u, t;
-  int rc;
-  unsigned f = 0;
-#define f_range 1u
+  unsigned long sz, a = 0, m = 0;
+  int ch, rc;
 
-  if (tvec_readword(tv, &d, ";", "buffer length")) { rc = -1; goto end; }
-  if (parse_unsigned_integer(&u, &q, d.buf)) goto bad;
-  if (!*q) {
-    tvec_skipspc(tv); pos = d.len;
-    if (!tvec_readword(tv, &d, ";", 0)) pos++;
-    q = d.buf + pos;
-  }
+  if (parse_size(tv, &sz, "@;", "buffer length")) { rc = -1; goto end; }
+  if (check_string_length(sz, rd->arg.p, tv)) { rc = -1; goto end; }
 
-  if (u > (size_t)-1) goto rangerr;
-  for (t = u, unit = units; *unit; unit++) {
-    if (t > (size_t)-1/1024) f |= f_range;
-    else t *= 1024;
-    if (*q == *unit) {
-      if (f&f_range) goto rangerr;
-      u = t; q++; break;
-    }
+  tvec_skipspc(tv);
+  ch = getc(tv->fp);
+  if (ch == ';' || ch == '\n') { ungetc(ch, tv->fp); goto done; }
+  else if (ch != '@') { rc = tvec_syntax(tv, ch, "`@'"); goto end; }
+
+  if (parse_size(tv, &m, "+;", "alignment quantum")) { rc = -1; goto end; }
+  if (m == 1) m = 0;
+
+  tvec_skipspc(tv);
+  ch = getc(tv->fp);
+  if (ch == ';' || ch == '\n') { ungetc(ch, tv->fp); goto done; }
+  else if (ch != '+') { rc = tvec_syntax(tv, ch, "`+'"); goto end; }
+
+  if (parse_size(tv, &a, ";", "alignment offset")) { rc = -1; goto end; }
+  if (a >= m) {
+    rc = tvec_error(tv, "alignment offset %lu >= quantum %lu",
+                   (unsigned long)a, (unsigned long)m);
+    goto end;
   }
-  if (*q == 'B') q++;
-  if (*q) goto bad;
-  if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
 
+done:
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
-  allocate_buffer(rv, u);
+  rv->buf.sz = sz; rv->buf.a = a; rv->buf.m = m;
   rc = 0;
 end:
-  DDESTROY(&d); return (rc);
-
-bad:
-  tvec_error(tv, "invalid buffer length `%s'", d.buf);
-  rc = -1; goto end;
-
-rangerr:
-  tvec_error(tv, "buffer length `%s' out of range", d.buf);
-  rc = -1; goto end;
-
-#undef f_range
+  return (rc);
 }
 
 /* --- @dump_buffer@ --- *
@@ -3352,22 +3450,80 @@ static void dump_buffer(const union tvec_regval *rv,
                        unsigned style,
                        const struct gprintf_ops *gops, void *go)
 {
-  const char *unit;
-  unsigned long u = rv->bytes.sz;
-
-  if (!u || u%1024)
-    gprintf(gops, go, "%lu B", u);
-  else {
-    for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
-    gprintf(gops, go, "%lu %cB", u, *unit);
+  format_size(gops, go, rv->buf.sz, style);
+  if (rv->buf.m) {
+    gprintf(gops, go, style&TVSF_COMPACT ? "@" : " @ ");
+    format_size(gops, go, rv->buf.m, style);
+    if (rv->buf.a) {
+      gprintf(gops, go, style&TVSF_COMPACT ? "+" : " + ");
+      format_size(gops, go, rv->buf.a, style);
+    }
+  }
+  if (!(style&TVSF_COMPACT)) {
+    gprintf(gops, go, " ; = %lu", rv->buf.sz);
+    if (rv->buf.m) {
+      gprintf(gops, go, " @ %lu", rv->buf.m);
+      if (rv->buf.a) gprintf(gops, go, " + %lu", rv->buf.a);
+    }
+    gprintf(gops, go, " = "); format_unsigned_hex(gops, go, rv->buf.sz);
+    if (rv->buf.m) {
+      gprintf(gops, go, " @ "); format_unsigned_hex(gops, go, rv->buf.m);
+      if (rv->buf.a) {
+       gprintf(gops, go, " + ");
+       format_unsigned_hex(gops, go, rv->buf.a);
+      }
+    }
   }
 }
 
 /* Buffer type definition. */
 const struct tvec_regty tvty_buffer = {
-  init_bytes, release_bytes, eq_buffer,
+  init_buffer, release_buffer, eq_buffer,
   tobuf_buffer, frombuf_buffer,
   parse_buffer, dump_buffer
 };
 
+/* --- @tvec_initbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const union tvec_regval *src@ = source buffer
+ *             @size_t sz@ = size to allocate
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize the alignment parameters in @rv@ to match @src@,
+ *             and the size to @sz@.
+ */
+
+void tvec_initbuffer(union tvec_regval *rv,
+                    const union tvec_regval *src, size_t sz)
+  { rv->buf.sz = sz; rv->buf.a = src->buf.a; rv->buf.m = src->buf.m; }
+
+/* --- @tvec_allocbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
+ *             distinctive pattern.
+ */
+
+void tvec_allocbuffer(union tvec_regval *rv)
+{
+  unsigned char *p; size_t n;
+
+  if (rv->buf.p) xfree(rv->buf.p - rv->buf.off);
+
+  if (rv->buf.m < 2) {
+    rv->buf.p = xmalloc(rv->buf.sz); rv->buf.off = 0;
+  } else {
+    p = xmalloc(rv->buf.sz + rv->buf.m - 1);
+    n = (size_t)p%rv->buf.m;
+    rv->buf.off = (rv->buf.a - n + rv->buf.m)%rv->buf.m;
+    rv->buf.p = p + rv->buf.off;
+  }
+  memset(rv->buf.p, '?', rv->buf.sz);
+}
+
 /*----- That's all, folks -------------------------------------------------*/
index 06a12113c89a121f81ba880c5509fac9f0292e4b..c0cb3c3ecd822b325f169f82c0c9c86657eab9c0 100644 (file)
@@ -8,17 +8,7 @@ tvec \- test vector framework
 
 .SH SYNOPSIS
 .nf
-.B "#include <mLib/tvec.h>
-
-
-
-extern int tvec_serialize(const struct tvec_reg */*rv*/,
-                         const struct tvec_regdef */*regs*/,
-                         unsigned /*nr*/, size_t /*regsz*/,
-                         void **/*p_out*/, size_t */*sz_out*/);
-
-extern int tvec_deserialize(struct tvec_reg */*rv*/,
-                           const struct tvec_regdef */*regs*/,
-                           unsigned /*nr*/, size_t /*regsz*/,
-                           const void */*p*/, size_t /*sz*/);
+.B "#include <mLib/tvec.h>"
 
+.B "union tvec_misc {"
+.B 
index 1e1e154bd5ca8956d5290932b420fc4037006165..b2ae674d6bec87bbd2b92852974a76e16df5db24 100644 (file)
@@ -194,6 +194,11 @@ union tvec_regval {
   double f;                            /* floating point */
   struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */
   struct { char *p; size_t sz; } text; /* text string */
+  struct {                             /* buffer */
+    unsigned char *p; size_t sz;       /* binary string */
+    size_t a, m;                       /* residue and modulus */
+    size_t off;                                /* offset into full buffer */
+  } buf;
 #ifdef TVEC_REGSLOTS
   TVEC_REGSLOTS
 #endif
@@ -514,7 +519,7 @@ enum {
   TVBU_BYTE,                           /* counting bytes (@rbuf >= 0@) */
   TVBU_LIMIT                           /* (number of units) */
 };
-struct bench_timing;                   /* forward declaration */
+struct bench_timing;    /* include <mLib/bench.h> for the real definition */
 
 struct tvec_outops {
   /* Output operations. */
@@ -1727,6 +1732,7 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/);
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @dstr *d@ = string to append the word to
+ *             @const char **p_inout@ = pointer into string, updated
  *             @const char *delims@ = additional delimiters to stop at
  *             @const char *expect@, @va_list ap@ = what was expected
  *
@@ -1749,14 +1755,23 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/);
  *             word constituents, a null terminator is written to @d@, and
  *             it is safe to treat the string in @d@ as being null-
  *             terminated.
+ *
+ *             If @p_inout@ is not null, then @*p_inout@ must be a pointer
+ *             into @d->buf@, which will be adjusted so that it will
+ *             continue to point at the same position even if the buffer is
+ *             reallocated.  As a subtle tweak, if @*p_inout@ initially
+ *             points at the end of the buffer, then it will be adjusted to
+ *             point at the beginning of the next word, rather than at the
+ *             additional intervening space.
  */
 
-extern PRINTF_LIKE(4, 5)
+extern PRINTF_LIKE(5, 6)
   int tvec_readword(struct tvec_state */*tv*/, dstr */*d*/,
-                   const char */*delims*/, const char */*expect*/, ...);
+                   const char **/*p_inout*/, const char */*delims*/,
+                   const char */*expect*/, ...);
 extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/,
-                          const char */*delims*/, const char */*expect*/,
-                          va_list */*ap*/);
+                          const char **/*p_inout*/, const char */*delims*/,
+                          const char */*expect*/, va_list */*ap*/);
 
 /*----- Integer types: signed and unsigned --------------------------------*/
 
@@ -2419,13 +2434,24 @@ extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
 /*----- Buffer type -------------------------------------------------------*/
 
 /* Buffer registers are primarily used for benchmarking.  Only a buffer's
- * size is significant: its contents are ignored on comparison and output,
- * and unspecified on input.
+ * allocation parameters are significant: its contents are ignored on
+ * comparison and output, and unspecified on input.
+ *
+ * The input format gives the buffer's size, and an optional alignment
+ * specification, in the form %|SZ [`@' M [`+' A]]|%.  Each of %|SZ|%, %|M|%
+ * and %|A|% are sizes, as an integer, optionally suffixed with a unit `kB',
+ * `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or without the `B')
+ * denoting a power of 1024.  The %|SZ|% gives the (effective) buffer size.
+ * %|M|% is the `alignment quantum' and %|A|% is the `alignment offset'; both
+ * default to zero, but if %|M|% is nonzero then the start of the buffer is
+ * aligned such that it is %|A|% more than a multiple of %|M|% bytes.  Note
+ * that %|M|% need not be a power of two, though this is common.
  *
- * The input is simply the buffer size, as an integer, optionally suffixed
- * with a unit `kB', `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or
- * without the `B') denoting a power of 1024.  Units are used on output only
- * when the size would be expressed exactly.
+ * Units other than `B' are used on output only when the size would be
+ * expressed exactly.
+ *
+ * Buffers are %%\emph{not}%% allocated by default.  In benchmarks, this is
+ * best done in a @before@ function.
  *
  * No @claimeq@ functions or macros are provided for buffers because they
  * don't seem very useful.
@@ -2433,6 +2459,33 @@ extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
 
 extern const struct tvec_regty tvty_buffer;
 
+/* --- @tvec_initbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const union tvec_regval *src@ = source buffer
+ *             @size_t sz@ = size to allocate
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize the alignment parameters in @rv@ to match @src@,
+ *             and the size to @sz@.
+ */
+
+extern void tvec_initbuffer(union tvec_regval */*rv*/,
+                           const union tvec_regval */*src*/, size_t /*sz*/);
+
+/* --- @tvec_allocbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
+ *             distinctive pattern.
+ */
+
+extern void tvec_allocbuffer(union tvec_regval */*rv*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
index b541cb8c83f4cdc7d018733e78def4920745ea28..1a405871c4849f7b550710e32077fe9b7ba66533 100644 (file)
@@ -17,22 +17,21 @@ trace \- configurable tracing output
 .B "#include <mLib/trace.h>"
 
 .BI "void trace(unsigned " l ", const char *" f ", ...);"
-.ds mT \fBvoid trace_block(
-.BI "\*(mTunsigned " l ", const char *" s ,
-.BI "\h'\w'\*(mT'u'const void *" b ", size_t " sz );
+.ta \w'\fBvoid trace_block('u
+.BI "void trace_block(unsigned " l ", const char *" s ,
+.BI "  const void *" b ", size_t " sz );
 
 .BI "void trace_on(FILE *" fp ", unsigned " l );
-.ds mT \fBvoid trace_custom(
-.ds mU \*(mTvoid (*\fIfunc\fB)(
-.BI "\*(mUconst char *" buf ,
-.BI "\h'\w'\*(mU'u'size_t " sz ", void *" v ),
-.BI "\h'\w'\*(mT'u'void *" v );
+.ta \w'\fBvoid trace_custom('u +\w'\fBvoid (*\,\fIfunc\/\fB)('u
+.BI "void trace_custom(void (*" func ")(const char *" buf ,
+.BI "          size_t " sz ", void *" v ),
+.BI "  void *" v );
 .BI "void trace_level(unsigned " l );
 .BI "unsigned tracing(void);"
 
-.ds mT \fBunsigned traceopt(
-.BI "\*(mTconst trace_opt *" t ", const char *" p ,
-.BI "\h'\w'\*(mT'u'unsigned " f ", unsigned " bad );
+.ta \w'\fBunsigned traceopt('u
+.BI "unsigned traceopt(const trace_opt *" t ", const char *" p ,
+.BI "  unsigned " f ", unsigned " bad );
 
 .BI T( statements\fR... )
 .BI "IF_TRACING(unsigned " l ", " statements\fR... )
index f186e7d2f9b67c7f42f9878d05155d83672d29af..253b63523a3f489c6f7355a8e9ec2cd1be9b2ece 100644 (file)
@@ -7,20 +7,21 @@ mdwopt \- command-line option parser
 .nf
 .B "#include <mLib/mdwopt.h>"
 
+.ta 2n
 .B "typedef struct {"
-.B "\h'4n'char *arg, *prog;"
-.B "\h'4n'int opt, ind, err;"
-.B "\h'4n'..."
+.B "   char *arg, *prog;"
+.B "   int opt, ind, err;"
+.B "   ..."
 .B "} mdwopt_data;"
 
 .B "char *optarg, optprog;"
 .B "int optopt, opterr, optind;"
 
 .B "struct option {"
-.B "\h'4n'const char *name;"
-.B "\h'4n'int has_arg;"
-.B "\h'4n'int *flag;"
-.B "\h'4n'int val;"
+.B "   const char *name;"
+.B "   int has_arg;"
+.B "   int *flag;"
+.B "   int val;"
 .B "};"
 
 .B "#define OPTF_NOARG = ..."
@@ -40,23 +41,23 @@ mdwopt \- command-line option parser
 
 .B "#define OPTF_NEGATED = ..."
 
-.ds mT \fBint mdwopt(
-.BI "\*(mTint " argc ", char *const *" argv ,
-.BI "\h'\w'\*(mT'u'const char *" shortopt ,
-.BI "\h'\w'\*(mT'u'const struct option *" longopt ", int *" longind ,
-.BI "\h'\w'\*(mT'u'mdwopt_data *" data ", int " flags );
+.ta \w'\fBint mdwopt('u
+.BI "int mdwopt(int " argc ", char *const *" argv ,
+.BI "  const char *" shortopt ,
+.BI "  const struct option *" longopt ", int *" longind ,
+.BI "  mdwopt_data *" data ", int " flags );
 
 .BI "int getopt(int " argc ", char *const *" argv ", const char *" o );
 
-.ds mT \fBint getopt_long(
-.BI "\*(mTint " argc ", char *const *" argv ,
-.BI "\h'\w'\*(mT'u'const char * "shortopt ,
-.BI "\h'\w'\*(mT'u'const struct option *" longopt ", int *" longind );
+.ta \w'\fBint getopt_long('u
+.BI "int getopt_long(int " argc ", char *const *" argv ,
+.BI "  const char * "shortopt ,
+.BI "  const struct option *" longopt ", int *" longind );
 
-.ds mT \fBint getopt_long_only(
-.BI "\*(mTint " argc ", char *const *" argv ,
-.BI "\h'\w'\*(mT'u'const char * "shortopt ,
-.BI "\h'\w'\*(mT'u'const struct option *" longopt ", int *" longind );
+.ta \w'\fBint getopt_long_only('u
+.BI "int getopt_long_only(int " argc ", char *const *" argv ,
+.BI "  const char * "shortopt ,
+.BI "  const struct option *" longopt ", int *" longind );
 .fi
 .SH "OVERVIEW"
 The
index 240a046818c353dfafc2b342f45aef8b2fd41b1a..8deae22aa21a6fb6bac9b5507adf10877a5bc847 100644 (file)
@@ -56,7 +56,7 @@ EXTRA_DIST            += t/bits-testgen
 
 ## Control flow.
 pkginclude_HEADERS     += control.h
-##LIBMANS              += control.3
+LIBMANS                        += control.3
 
 check_PROGRAMS         += t/control.t
 t_control_t_SOURCES     = t/control-test.c
@@ -75,16 +75,16 @@ t_exc_t_LDFLAGS              = -static
 ## Generalized formatting.
 pkginclude_HEADERS     += gprintf.h
 libutils_la_SOURCES    += gprintf.c
-##LIBMANS              += gprintf.3
+LIBMANS                        += gprintf.3
 
 ## Linear regression.
 pkginclude_HEADERS     += linreg.h
 libutils_la_SOURCES    += linreg.c
-##LIBMANS              += linreg.3
+LIBMANS                        += linreg.3
 
 ## Mathematics.
 pkginclude_HEADERS     += maths.h
-##LIBMANS              += maths.3
+LIBMANS                        += maths.3
 
 ## String handling.
 pkginclude_HEADERS     += str.h
index 79e7df90e256813b3d21c7fffd73800d1b978282..be83be91fe4cdb067ef725857dafe99e8445aaac 100644 (file)
@@ -1,5 +1,18 @@
 .\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
 .TH control 3 "23 April 2023" "Straylight/Edgeware" "mLib utilities library"
+.
 .SH NAME
 control \- control structure metaprogramming
 .\" @MC_BEFORE
@@ -17,6 +30,7 @@ control \- control structure metaprogramming
 .\" @MC_ACT
 .\" @MC_LABEL
 .\" @MC_GOTO
+.
 .SH SYNOPSIS
 .nf
 .B "#include <mLib/control.h>"
@@ -24,9 +38,11 @@ control \- control structure metaprogramming
 .BI MC_BEFORE( tag ", " stmts ") " body
 .BI MC_AFTER( tag ", " stmts ") " body
 .BI MC_WRAP( tag ", " before_stmt ", " onend_stmt ", " onbreak_stmt ") " body
+.BI MC_FINALLY( tag ", " cleanup ") " body
 .BI MC_DOWHILE( tag ", " cond ") " body
 .BI MC_DECL( tag ", " decl ") " body
 .BI MC_LOOPELSE( tag ", " head ") " loop_body " \fR[\fBelse " else_body \fR]
+.BI MC_LOOPBETWEEN( tag ", " setup ", " cond ", " step ") " loop_body " \fR[\fBelse " else_body \fR]
 
 .BI MC_TARGET( tag ", " stmt ") " body
 .BI MC_GOTARGET( tag );
@@ -37,80 +53,95 @@ control \- control structure metaprogramming
 .BI MC_LABEL( tag )
 .BI MC_GOTO( tag )
 .fi
+.
 .SH DESCRIPTION
 The header file
 .B <mLib/control.h>
-defines a number of macros which are useful when defining new
-control structures for C.  They are inspired by Simon Tatham's article
+defines a number of macros which are useful
+when defining new control structures for C.
+They are inspired by Simon Tatham's article
 .IR "Metaprogramming custom control structures in C",
 though these macros differ from Tatham's in a few respects.
+.
 .SS "Common features"
 Each of these macros takes a
 .I tag
-argument.  A
+argument.
+A
 .I tag
-is lexically like an identifier, except that it may begin with a digit,
-so, for example, plain integers are acceptable tags.  Each use of an
-action macro by a user-level macro must have a distinct
+is lexically like an identifier,
+except that it may begin with a digit,
+so, for example, plain integers are acceptable tags.
+Each use of an action macro by a user-level macro
+must have a distinct
 .IR tag .
-If you're writing a new prefix action macro written in terms of these
-existing actions, your macro should receive a
+If you're writing a new prefix action macro
+written in terms of these existing actions,
+your macro should receive a
 .I tag
-from its caller, and pass this tag, along with a distinctive component
-of its own, down to any prefix actions that it calls; the
+from its caller,
+and pass this tag,
+along with a distinctive component of its own,
+down to any prefix actions that it calls;
+the
 .IR tag s
 from each layer should be separated by a pair of underscores.
 .PP
 Some of these macros work by wrapping a loop around the
 .I body
-statement.  This interferes with the way that `free'
+statement.
+This interferes with the way that `free'
 .B break
 and
 .B continue
 statements within the
 .I body
-behave: we say that these statements are
+behave:
+we say that these statements are
 .I captured
 by the macro.
 A
 .B break
 or
 .B continue
-statement is `free' if it doesn't appear lexically within a loop or
+statement is
+.I free
+if it doesn't appear lexically within a loop or
 (for
 .B break)
 .B switch
 statement that is part of the
 .IR body .
 So
-.IP
-.B "if (!x) break;"
-.PP
+.VS
+if (!x) break;
+.VE
 contains a free
 .B break
-statement, while
-.IP
-.nf
-.ft B
+statement,
+while
+.VS
+.ta 2n
 for (i = 0; i < n; i++)
-\h'4n'if (interestingp(i)) break;
-.ft
-.fi
-.PP
+       if (interestingp(i)) break;
+.VE
 does not.
 .PP
-Some of these macros take special care to give you control over what
-happens when a captured
+Some of these macros take special care
+to give you control over what happens when a captured
 .B break
-is executed.  Alas, proper handling of
+is executed.
+Alas, proper handling of
 .B continue
-doesn't seem possible.  Free
+doesn't seem possible.
+Free
 .B break
 and
 .B continue
 statements
 .I within
 arguments to these macros are never captured.
+.
 .SS "Prefix action macros"
 .B MC_BEFORE
 macro is the simplest to understand.  Executing
@@ -121,8 +152,9 @@ has the same effect as executing
 .I stmt
 followed by
 .IR body ,
-except that the whole thing is syntactically a single statement, so, for
-example, it doesn't need to be enclosed in braces to be the body of a
+except that the whole thing is syntactically a single statement,
+so, for example, it doesn't need to be enclosed in braces
+to be the body of a
 .B for
 loop.
 .B MC_BEFORE
@@ -142,7 +174,8 @@ the same effect as executing
 .I stmt
 followed by
 .IR body .
-Again, the whole thing is syntactically a single statement.  However,
+Again, the whole thing is syntactically a single statement.
+However,
 .B MC_AFTER
 captures free
 .B break
@@ -178,15 +211,46 @@ statement, then control abruptly continues with the
 .I onbreak
 statement, and
 .I onend
-is not executed.  Currently, if the
+is not executed.
+Currently, if the
 .I body
 executes a free
 .B continue
-statement, then control abruptly continues with the
+statement,
+then control abruptly continues with the
 .I onend
-statement, but this behaviour is a bug and may be fixed in the future.
+statement,
+but this behaviour is a bug and may be fixed in the future.
+.PP
+Executing
+.IP
+.BI MC_FINALLY( tag ", " cleanup ") " body
 .PP
-.\" @@@ mc_finally
+has the same effect as executing
+.I body
+followed by
+.IR cleanup ,
+except that a free
+.B break
+statement within
+.I body
+will execute
+.I cleanup
+before propagating the
+.B break
+to the enclosing context.
+A free
+.B continue
+statement currently causes control to continue abruptly with
+.I cleanup
+but this behaviour is a bug and may be fixed in the future.
+The
+.I cleanup
+code is textually duplicated,
+so there'll be some code bloat if this is very complex.
+If it arranges to have private long-term state
+then the two copies will not share this state,
+so probably don't do this.
 .PP
 Executing
 .IP
@@ -206,7 +270,8 @@ except that free
 .B break
 and
 .B continue
-statements are captured.  Currently, a free
+statements are captured.
+Currently, a free
 .B continue
 statement will simply abruptly terminate execution of the
 .IR body ,
@@ -224,47 +289,56 @@ The
 .B MC_DECL
 macro makes use of the fact that a
 .B for
-statement can introduce a declaration into its body's scope in C99 and
-C++; the macro is not available in C89.
+statement can introduce a declaration
+into its body's scope in C99 and C++;
+the macro is not available in C89.
 .PP
 Executing
 .IP
 .nf
+.ta 2n
 .BI MC_LOOPELSE( head ", " tag ") "
-.RI \h'4n' loop_body
+.I "   loop_body"
 .RB [ else
-.RI \h'4n' else_body ]
+.IR "  else_body" ]
 .fi
 .PP
-results in Python-like loop behaviour.  The
+results in Python-like loop behaviour.
+The
 .I head
 must be a valid loop head with one of the forms
 .IP
+.nf
 .BI "while (" cond ")"
-.br
 .BI "for (" decl "; " cond "; " next_expr ")"
-.br
 .BI "MC_DOWHILE(" tag ", " cond ")"
+.fi
 .PP
 The resulting loop executes the same as
 .IP
 .nf
+.ta 2n
 .I head
-.RI \h'4n' loop_body
+.I "   loop_body"
 .fi
 .PP
 If the loop ends abruptly, as a result of
 .BR break ,
-then control is passed to the statement following the loop in the usual
-way.  However, if the loop completes naturally, and the optional
+then control is passed to the statement following the loop
+in the usual way.
+However, if the loop completes naturally,
+and the optional
 .B else
-clause is present, then the
+clause is present,
+then the
 .I else_body
-is executed.  A free
+is executed.
+A free
 .B continue
 statement within the
 .I loop_body
-behaves normally.  Free
+behaves normally.
+Free
 .B break
 and
 .B continue
@@ -272,7 +346,63 @@ statements within the
 .I else_body
 are not captured.
 .PP
-.\" @@@ loopbetween
+Executing
+.IP
+.nf
+.ta 2n
+.BI MC_LOOPBETWEEN( tag ", " setup ", " cond ", " step ") "
+.I "   loop-body"
+.RB [ else
+.IR "  else-body" ]
+.fi
+.PP
+is similar to executing the
+.B for
+loop
+.IP
+.ta 2n
+.nf
+.BI "for (" setup "; " cond "; " step ") "
+.I "   loop-body"
+.fi
+.PP
+except that, once the
+.I loop_body
+has finished,
+the
+.I step
+expression evaluated,
+and the
+.I cond
+evaluated and determined to be nonzero,
+the
+.I else_body
+(if any) is executed before re-entering the
+.IR loop_body .
+This makes it a useful place to insert
+any kind of interstitial material,
+e.g., printing commas between list items.
+Note that by the time the
+.I else_body
+is executed,
+the decision has already been made
+that another iteration will be performed,
+and, in particular, the
+.I step
+has occurred.  The
+.I else_body
+is therefore looking at the next item to be processed,
+not the item that has just finished being processed.
+The
+.I cond
+is textually duplicated,
+so there'll be some code bloat if this is very complex.
+If it somehow manages to have private long-term state
+(e.g., as a result of declaring static variables
+inside GCC statement expressions)
+then the two copies will not share this state,
+so probably don't do this.
+.
 .SS "Lower-level machinery"
 Executing
 .IP
@@ -284,7 +414,8 @@ Executing
 .B MC_GOTARGET
 immediately transfers control to
 .IR stmt ,
-with control continuing with the following statement, skipping the
+with control continuing with the following statement,
+skipping the
 .IR body .
 Free
 .B break
@@ -294,16 +425,24 @@ statements in
 .I body
 are not captured.
 .PP
-This is most commonly useful in loops in order to arrange the correct
-behaviour of a free
+This is most commonly useful in loops
+in order to arrange the correct behaviour of a free
 .B break
-within the loop body.  See the example below, which shows the definition
+within the loop body.
+See the example below,
+which shows the definition
 of
 .BR MC_LOOPELSE .
 .PP
 Executing
 .IP
-.BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR]
+.nf
+.ta 2n
+.BI MC_ALLOWELSE( tag ") "
+.I "   main_body"
+.RB [ else
+.IR "  else_body" ]
+.fi
 .PP
 has exactly the same effect as just
 .IR main_body .
@@ -313,8 +452,9 @@ Executing
 .PP
 transfers control immediately to
 .I else_body
-(if present); control then naturally transfers to the following
-statement as usual.  Free
+(if present);
+control then naturally transfers to the following statement as usual.
+Free
 .B break
 or
 .B continue
@@ -334,40 +474,41 @@ so things will likely to wrong if
 .I main_body
 is itself an
 .B if
-statement: if
+statement:
+if
 .I main_body
 lacks an
 .B else
-clause, then an
+clause,
+then an
 .B else
 intended to match
 .B MC_ALLOWELSE
-will be mis-associated; and even if
+will be mis-associated;
+and even if
 .I main_body
 .I does
 have an
 .B else
-clause, the resulting program text is likely to provoke a compiler
-warning about `dangling
+clause,
+the resulting program text is likely to provoke a compiler warning
+about `dangling
 .BR else '.
 .PP
-Using these tools, it's relatively straightforward to define a macro
-like
+Using these tools,
+it's relatively straightforward to define a macro like
 .BR MC_LOOPELSE ,
 described above:
-.IP
-.nf
-.ft B
-#define MC_LOOPELSE(tag, head) \e
-\h'4n'MC_TARGET(tag##__exit, { ; }) \e
-\h'4n'MC_ALLOWELSE(tag##__else) \e
-\h'4n'MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \e
-\h'4n'head \e
-\h'8n'MC_WRAP(tag##__body, { ; }, { ; }, \e
-\h'8n+\w'MC_WRAP(tag##__body, ''{ MC_GOTARGET(tag##__exit); })
-.ft R
-.fi
-.PP
+.VS
+.ta 4n 4n+\w'\fBMC_WRAP(tag##__body, 'u \n(.lu-\n(.iu-4n
+#define MC_LOOPELSE(tag, head)                 \e
+       MC_TARGET(tag##__exit, { ; })           \e
+       MC_ALLOWELSE(tag##__else)               \e
+       MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); })             \e
+       head            \e
+       MC_WRAP(tag##__body, { ; }, { ; },              \e
+               { MC_GOTARGET(tag##__exit); })
+.VE
 The main `trick' for these control-flow macros is
 .BR MC_ACT ,
 which wraps up a statement as an
@@ -380,7 +521,8 @@ or
 .BR while :
 i.e., it must be completed by following it with a
 .I body
-statement.  Executing
+statement.
+Executing
 .IP
 .BI MC_ACT( stmt ") " body
 .PP
@@ -388,9 +530,11 @@ has the same effect as simply executing
 .IR stmt ;
 the
 .I body
-is usually ignored.  Note that
+is usually ignored.
+Note that
 .B ;
-is a valid statement which does nothing, so
+is a valid statement which does nothing,
+so
 .BI MC_ACT( stmt );
 is also a valid statement with the same effect as
 .IR stmt .
@@ -413,12 +557,13 @@ immediately transfers control to the
 .IR body .
 Note that
 .B MC_GOTO
-is syntactically an action
-(i.e., it's wrapped in
-.BR MC_ACT ).
+is syntactically an action,
+i.e., it's wrapped in
+.BR MC_ACT .
 The
 .IR tag s
-here are scoped to the top-level source line, like all
+here are scoped to the top-level source line,
+like all
 .IR tag s
 in this macro package.
 .PP
@@ -431,26 +576,26 @@ sometimes with one or two other statement heads thrown into the mix.
 For example,
 .B MC_AFTER
 is defined as
-.IP
-.nf
-.ft B
-#define MC_AFTER(tag, stmt) \e
-\h'28n'MC_GOTO(tag##__body) \e
-\h'4n'MC_LABEL(tag##__end) \e
-\h'28n'MC_ACT(stmt) \e
-\h'28n'for (;;) \e
-\h'32n'MC_GOTO(tag##__end) \e
-\h'4n'MC_LABEL(tag##__body)
-.ft R
-.fi
-.PP
-(The unusual layout is conventional, to make the overall structure of
-the code clear despite visual interference from the labels.)
+.VS
+.ta 4n 28n 30n \n(.lu-\n(.iu-4n
+#define MC_AFTER(tag, stmt)                    \e
+               MC_GOTO(tag##__body)            \e
+       MC_LABEL(tag##__end)                    \e
+               MC_ACT(stmt)            \e
+               for (;;)                \e
+                       MC_GOTO(tag##__end)     \e
+       MC_LABEL(tag##__body)
+.VE
+(The unusual layout is conventional,
+to make the overall structure of the code clear
+despite visual interference from the labels.)
 The
 .I body
-appears at the end, labelled as
+appears at the end,
+labelled as
 .IB tag __body \fR.
-Control enters at the start, and is immediately transferred to the
+Control enters at the start,
+and is immediately transferred to the
 .I body ;
 but the
 .I body
@@ -458,7 +603,8 @@ is enclosed in a
 .B for
 loop, so when the
 .I body
-completes, the loop restarts, transferring control to
+completes, the loop restarts,
+transferring control to
 .IB tag __end
 and the
 .IR stmt .
@@ -466,21 +612,33 @@ Since it is enclosed in
 .BR MC_ACT ,
 once
 .I stmt
-completes, control transfers to the following statement.
-.SH BUGS
+completes,
+control transfers to the following statement.
+.
+.SH "BUGS"
 Some macros cause free
 .B break
 and/or
 .B continue
 statements to behave in unexpected ways.
 .PP
-The need for tagging is ugly, and the restriction on having two
-user-facing control-flow macros on the same line is objectionable.  The
-latter could be avoided by using nonstandard features such as GCC's
+It's rather hard to use
+.B MC_ALLOWELSE
+in practice without provoking
+.RB `dangling- else '
+warnings.
+.PP
+The need for tagging is ugly,
+and the restriction on having two
+user-facing control-flow macros on the same line is objectionable.
+The latter could be avoided
+by using nonstandard features such as GCC's
 .B __COUNTER__
-macro, but adopting that would do programmers a disservice by
-introducing a hazard for those trying to port code to other compilers
-which lack any such feature.
+macro,
+but adopting that would do programmers a disservice
+by introducing a hazard for those
+trying to port code to other compilers which lack any such feature.
+.
 .SH "SEE ALSO"
 .BR mLib (3),
 .BR macros (3).
@@ -488,5 +646,6 @@ which lack any such feature.
 Simon Tatham,
 .IR "Metaprogramming custom control structures in C",
 .BR "https://www.chiark.greenend.org.uk/~sgtatham/mp/" .
+.
 .SH "AUTHOR"
 Mark Wooding, <mdw@distorted.org.uk>
diff --git a/utils/gprintf.3 b/utils/gprintf.3
new file mode 100644 (file)
index 0000000..c7f8652
--- /dev/null
@@ -0,0 +1,227 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.
+.TH gprintf 3 "9 March 2024" "Straylight/Edgeware" "mLib utilities library"
+.
+.SH NAME
+gprintf \- generalized output formatting
+.
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/gprintf.h>"
+
+.ta 2n
+.B "struct gprintf_ops {"
+.BI "  int (*putch)(void *" out ", int " ch ");"
+.BI "  int (*putm)(void *" out ", const char *" p ", size_t " sz ");"
+.BI "  int (*nputf)(void *" out ", size_t " maxsz ", const char *" p ", ...);"
+.B "};"
+
+.BI "int gprintf(const struct gprintf_ops *" ops ", void *" out ","
+.ta \w'\fBint gprintf('u
+.BI "  const char *" p ", ...);"
+.BI "int vgprintf(const struct gprintf_ops *" ops ", void *" out ","
+.ta \w'\fBint vgprintf('u
+.BI "  const char *" p ", va_list *" ap ");"
+
+.BI "int gprintf_memputf(char **" buf_inout ", size_t *" sz_inout ","
+.ta \w'\fBint gprintf_memputf('u
+.BI "  size_t " maxsz ", const char *" p ", va_list " ap ");"
+
+.B "const struct gprintf_ops file_printops;"
+.fi
+.
+.SH DESCRIPTION
+The
+.B "<mLib/gprintf.h>"
+header file declares facilities for generalized output formatting
+\(en i.e.,
+.BR printf (3)-like
+formatting to arbitrary output sinks.
+This is the mechanism underlying the
+.BR dstr_putf (3)
+and
+.BR buf_putstrf...(3)
+functions.
+.PP
+To use it, you must define a
+.B "struct gprintf_ops"
+structure,
+providing functions to write the formatted output to your chosen sink.
+Each function receives a void pointer argument named
+.IR out ,
+which is simply the
+.I out
+argument passed to
+.RB ( v ) gprintf ,
+and should return the number of characters that it wrote
+\(en or at least some nonnegative value \(en
+on success,
+or \-1 if it encountered an error.
+.PP
+The three functions are:
+.hP \*o
+.BR putch :
+write out the single character
+.IR ch ,
+which is an integer holding an
+.B "unsigned char"
+value, as used by
+.BR fputc (3).
+.hP \*o
+.BR putm :
+write out
+.I sz
+characters from the buffer starting at
+.IR p .
+.hP \*o
+.BR nputf :
+process the format string
+.I p
+and arguments
+.IR ap ,
+writing out the formatted output;
+the output will not be longer than
+.I maxsz
+characters.
+.PP
+It may seem paradoxical for
+.B gprintf
+to require the backend to do string formatting,
+since its entire purpose is to do string formatting for you;
+but implementing the
+.B nputf
+function can typically be done straightforwardly enough by calling
+.BR snprintf (3)
+or similar.
+Difficult cases can be dealt with using
+.BR gprintf_memputf ,
+described below.
+.PP
+The
+.B gprintf
+function formats a string
+.I p
+together with its variable argument list,
+using the provided output operations
+.IR ops ,
+and passing them the pointer
+.I out
+when it calls on them.
+The
+.B vgprintf
+function is similar,
+except that it receives the format arguments as
+.I "a pointer to"
+a captured
+.B va_list
+argument tail.
+The argument tail is updated in place,
+and (on successful completion)
+is left referring to the first unused argument.
+.PP
+The
+.B gprintf_memputf
+function is a utility for implementing
+.B nputf
+operations.
+On entry,
+.BI * buf_inout
+should be a pointer to a buffer of
+.BI * sz_inout
+bytes, allocated from
+.BR arena_global (3);
+instead,
+.BI * buf_inout
+may be null
+if
+.BI * sz_inout
+is zero.
+The
+.I maxsz
+and
+.I p
+arguments are the maximum output size and format string passed to the
+.B nputf
+function,
+and
+.I ap
+is the format-argument list, captured using
+.BR va_start (3).
+The function will adjust the buffer pointer and size as necessary,
+write the formatted result to the buffer, null-terminated,
+and return the actual output length.
+The function is designed to be efficient when called multiple times,
+retaining the same buffer across calls,
+resizing it as necessary in a geometric progression.
+When the buffer is no longer wanted, free it using
+.BR xfree (3).
+.PP
+A typical
+.B nputf
+function using
+.B gprintf_memputf
+might look something like this.
+.VS
+.ta 2n
+struct my_output {
+       /* output state */
+       char *buf;
+       size_t sz;
+       /* ...\& other members ...\& */
+};
+
+/* ...\& define putch and putm ...\& */
+
+static int nputf(void *out, size_t maxsz, const char *p, ...)
+{
+       struct my_output *myout = out;
+       va_list ap;
+       int n;
+
+       va_start(ap, p);
+       n = gprintf_memputf(&myout->buf, &myout->sz, maxsz, p, ap);
+       va_end(ap);
+       if (n > 0) n = putm(myout, myout->buf, n);
+       return (n);
+}
+
+const struct gprintf_ops my_output_ops = { putch, putm, nputf };
+
+/* ...\& */
+
+struct my_output myout;
+
+myout.buf = 0; myout.sz = 0;
+/* ...\& other initialization ...\& */
+gprintf(&my_output_ops, &myout, "Hello, %s!", "world");
+xfree(myout.buf); myout.buf = 0; myout.sz = 0;
+/* ...\& other cleanup ...\& */
+.VE
+.
+.SH "SEE ALSO"
+.BR buf (3),
+.BR dstr (3),
+.BR mLib (3).
+.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
index 602698cdbbd096d1a4f1fedfd0df4d466060038b..9964d184ea35b786a3967e7ea7a4dcbb108a40c8 100644 (file)
@@ -48,6 +48,7 @@
 #include "darray.h"
 #include "dstr.h"
 #include "gprintf.h"
+#include "growbuf.h"
 #include "macros.h"
 
 /*----- Tunable constants -------------------------------------------------*/
@@ -408,8 +409,8 @@ int vgprintf(const struct gprintf_ops *ops, void *out,
     /* --- Output the literal portion --- */
 
     if (fs->n) {
-      if (ops->putm(out, fs->p, fs->n)) return (-1);
-      tot += fs->n;
+      n = ops->putm(out, fs->p, fs->n); if (n < 0) return (-1);
+      tot += n;
     }
 
     /* --- And now the variable portion --- */
@@ -419,8 +420,8 @@ int vgprintf(const struct gprintf_ops *ops, void *out,
        case 0:
          break;
        case '%':
-         if (ops->putch(out, '%')) return (-1);
-         tot++; break;
+         n = ops->putch(out, '%'); if (n < 0) return (-1);
+         tot += n; break;
        default:
          abort();
       }
@@ -500,8 +501,8 @@ int vgprintf(const struct gprintf_ops *ops, void *out,
        break;
 #else
 #  define MSG "<no float support>"
-       if (ops->putm(out, MSG, sizeof(MSG) - 1)) return (-1);
-       tot += sizeof(MSG) - 1; continue;
+       n = ops->putm(out, MSG, sizeof(MSG) - 1); if (n < 0) return (-1);
+       tot += n; continue;
 #  undef MSG
 #endif
       case 's':
@@ -526,14 +527,14 @@ int vgprintf(const struct gprintf_ops *ops, void *out,
     switch (fs->fmt) {
 #define CASE(code, ty)                                                 \
        case fmt_##code:                                                \
-         i = ops->nputf(out, sz, dd.buf, fa[fs->arg].u.code);          \
+         n = ops->nputf(out, sz, dd.buf, fa[fs->arg].u.code);          \
          break;
       OUTPUT_FMTTYPES(CASE)
 #undef CASE
       default: abort();
     }
-    if (i < 0) return (-1);
-    tot += i;
+    if (n < 0) return (-1);
+    tot += n;
   }
 
   /* --- We're done --- */
@@ -595,21 +596,13 @@ int gprintf(const struct gprintf_ops *ops, void *out, const char *p, ...)
 size_t gprintf_memputf(char **buf_inout, size_t *sz_inout,
                    size_t maxsz, const char *p, va_list ap)
 {
-  char *buf = *buf_inout;
-  size_t sz = *sz_inout;
   int n;
 
-  if (sz <= maxsz) {
-    if (!sz) sz = 32;
-    while (sz <= maxsz) sz *= 2;
-    if (buf) xfree(buf);
-    buf = xmalloc(sz); *buf_inout = buf; *sz_inout = sz;
-  }
-
+  GROWBUF_REPLACE(&arena_stdlib, *buf_inout, *sz_inout, maxsz, 64, 1);
 #ifdef HAVE_SNPRINTF
-  n = vsnprintf(buf, maxsz + 1, p, ap);
+  n = vsnprintf(*buf_inout, maxsz + 1, p, ap);
 #else
-  n = vsprintf(buf, p, ap);
+  n = vsprintf(*buf_inout, p, ap);
 #endif
   assert(0 <= n && n <= maxsz);
   return (n);
index 8c0f87c903f44320771a30c3aedb13a992cff697..7bb0b043d2f02d91637ded00beda3920730eb517 100644 (file)
@@ -53,38 +53,32 @@ extern const struct gprintf_ops file_printops;
 
 /*----- Functions provided ------------------------------------------------*/
 
-/* --- @vgprintf@ --- *
+/* --- @gprintf@, @vgprintf@ --- *
  *
  * Arguments:  @const struct gprintf_ops *ops@ = output operations
  *             @void *out@ = context for output operations
  *             @const char *p@ = pointer to @printf@-style format string
- *             @va_list *ap@ = argument handle
- *
- * Returns:    The number of characters written to the string.
- *
- * Use:                As for @gprintf@, but takes a reified argument tail.
- */
-
-extern int vgprintf(const struct gprintf_ops */*ops*/, void */*out*/,
-                   const char */*p*/, va_list */*ap*/);
-
-/* --- @gprintf@ --- *
- *
- * Arguments:  @const struct gprintf_ops *ops@ = output operations
- *             @void *out@ = context for output operations
- *             @const char *p@ = pointer to @printf@-style format string
- *             @...@ = argument handle
+ *             @...@ = format arguments
+ *             @va_list *ap@ = captured format-arguments tail
  *
  * Returns:    The number of characters written to the string.
  *
  * Use:                Formats a @printf@-like message and writes the result using
  *             the given output operations.  This is the backend machinery
  *             for @dstr_putf@, for example.
+ *
+ *             The @gprintf@ function receives its format arguments as a
+ *             variable-length argument tail; the @vgprintf@ function
+ *             receives them as %%\emph{a pointer to}%% a captured argument
+ *             tail; it updates @*ap@, leaving it ready to read the next
+ *             unused argument.
  */
 
 extern PRINTF_LIKE(3, 4)
   int gprintf(const struct gprintf_ops */*ops*/, void */*out*/,
              const char */*p*/, ...);
+extern int vgprintf(const struct gprintf_ops */*ops*/, void */*out*/,
+                   const char */*p*/, va_list */*ap*/);
 
 /* --- @gprintf_memputf@ --- *
  *
diff --git a/utils/linreg.3 b/utils/linreg.3
new file mode 100644 (file)
index 0000000..20f74ca
--- /dev/null
@@ -0,0 +1,73 @@
+.\" -*-nroff-*-
+.TH linreg 3 "9 March 2024" "Straylight/Edgeware" "mLib utilities library"
+.\" @linreg_init
+.\" @linreg_update
+.\" @linreg_fit
+.\" @LINREG_INIT
+.
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/linreg.h>"
+
+.B "struct linreg { ...\& };"
+.B "#define LINREG_INIT ..."
+
+.BI "void linreg_init(struct linreg *" lr );
+.BI "void linreg_update(struct linreg *" lr ", double " x ", double " y );
+.ta \w'void linreg_fit('u
+.BI "void linreg_fit(struct linreg *" lr ,
+.BI "  double *" m_out ", double *" c_out ", double *" r_out );
+.fi
+.
+.SH DESCRIPTION
+The functions declared in the
+.B <mLib/linreg.h>
+header perform simple linear regression.
+.PP
+The state for a linear regression is held in a
+.BR "struct linreg" .
+Such a structure can be initialized statically,
+using the
+.B LINREG_INIT macro,
+or dynamically, by calling the
+.B linreg_init
+function.
+.PP
+Once a state is initialized,
+points
+.RI ( x ",\ " y )
+can be added by calling
+.BR linreg_update .
+Each call just performs a small and constant amount of computation;
+the linear regression state uses a constant amount of storage
+independent of the number of points.
+.P
+Finally, the
+.B linreg_fit
+function will return the results of the regression.
+It calculates quantities
+.I m
+and
+.I c
+such that the line
+.IR y "\ =\ " m "\ " x "\ +\ " c
+is a reasonable approximation to the data points provided,
+and a correlation coefficient
+.I r
+quantifying how good this approximation is.
+These quantities are stored in
+.BI * m_out \fR,
+.BI * c_out \fR,
+and
+.BI * r_out \fR,
+respectively;
+any (or all, but that wouldn't be useful) of these pointers may be null,
+to discard the corresponding output.
+.PP
+The linear regression state can be discarded without need for ceremony:
+it holds no external resources.
+.PP
+Any half-decent introduction to statistics will explain these concepts.
+.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
index 2adb65f8062fca1b14a15710104e84d72ca3dab1..8b4d06d77d6a96286a81d4fd83d0aac14618b4e5 100644 (file)
@@ -97,8 +97,6 @@
  * which I'm not going to derive here.
  */
 
-/*----- Header files ------------------------------------------------------*/
-
 /*----- Data structures ---------------------------------------------------*/
 
 struct linreg {
diff --git a/utils/maths.3 b/utils/maths.3
new file mode 100644 (file)
index 0000000..0d6afa5
--- /dev/null
@@ -0,0 +1,36 @@
+.\" -*-nroff-*-
+.TH linreg 3 "9 March 2024" "Straylight/Edgeware" "mLib utilities library"
+.\" @NANPN
+.\" @INFP
+.\" @NEGP
+.
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/maths.h>"
+
+.BI "int NANP(" floatish " " x );
+.BI "int INFP(" floatish " " x );
+.BI "int NEGP(" floatish " " x );
+.fi
+.
+.SH DESCRIPTION
+The
+.B <mLib/maths.h>
+header declares some minor low-level floating-point utilities.
+These are mostly redundant with C99,
+but provided for portability to older platforms.
+.PP
+The
+.B NANP
+macro returns nonzero if its argument is not-a-number.
+The
+.B INFP
+macro returns nonzero if its argument is infinite.
+The
+.B NEGP
+macro returns nonzero if its argument is negative;
+on IEEE\ 754 platforms with sufficient support,
+it will correctly detect negative zero.
+.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>