.\" @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 );
.\" @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 );
.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
.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;"
.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 ..."
.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 );
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>
#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"
}
}
+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)
{ 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)
{
};
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 };
.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
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
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
.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 );
--- /dev/null
+.\" -*-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>
--- /dev/null
+/* -*-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
.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"
.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 );
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 )
.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
.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
.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;"
.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 );
.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
.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
.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
*/
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, ...)
{
..
.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
.\" @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
.\" @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
.\" @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
.\" @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
.\" @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
.\" @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
.\" @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
.\" @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
.\" @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
.\" @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 );
.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 );
.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
.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 ,
.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
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
.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.
.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>
/*----- Header files ------------------------------------------------------*/
-#include <assert.h>
#include <string.h>
#include "buf.h"
+#include "growbuf.h"
#include "macros.h"
/*----- Main code ---------------------------------------------------------*/
* 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@ --- *
*
* 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@ --- *
*
* 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@ --- *
*
*/
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@ --- *
*
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)
{
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);
}
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));
}
{ \
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); \
{
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);
*/
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@ --- *
*
*/
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; \
*/
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@ --- *
*
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@ --- *
*
/* --- @{,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).
}) \
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) \
#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); })
#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))
.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 ..."
#include "alloc.h"
#include "arena.h"
#include "darray.h"
+#include "growbuf.h"
/*----- Magic numbers -----------------------------------------------------*/
* 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 --- *
*
* 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 --- *
*
*/
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, ...)
{
#include "alloc.h"
#include "dstr.h"
+#include "growbuf.h"
/*----- Tunable constants -------------------------------------------------*/
*/
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@ --- *
*
.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;"
.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 );
.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
.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 );
.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 ");"
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
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;
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
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
#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[] = { \
{ 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
};
.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;"
.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
###--------------------------------------------------------------------------
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
#include <string.h>
#include "alloc.h"
+#include "growbuf.h"
#include "tvec.h"
/*----- Output ------------------------------------------------------------*/
*
* 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
*
* 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);
}
/* 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, "`]'");
*/
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);
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;
}
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 =
*/
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, ...)
{
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 =
#include "buf.h"
#include "compiler.h"
#include "fdflags.h"
+#include "growbuf.h"
#include "lbuf.h"
#include "mdup.h"
#include "quis.h"
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;
}
*/
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@ --- *
*
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);
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;
}
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);
}
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);
}
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 ------------------------------------------*/
*
* 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,
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");
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");
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");
/* 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));
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;
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"))
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);
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);
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;
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;
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; }
{
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;
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);
}
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; } \
/* 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. */
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));
}
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);
}
*/
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);
/*----- 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@ --- *
*
*
* 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@ --- *
*
*
* 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@ --- *
*
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);
}
* 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@ --- *
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 -------------------------------------------------*/
.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
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
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. */
*
* 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
*
* 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 --------------------------------*/
/*----- 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.
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
.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... )
.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 = ..."
.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
## 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
## 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
.\" -*-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
.\" @MC_ACT
.\" @MC_LABEL
.\" @MC_GOTO
+.
.SH SYNOPSIS
.nf
.B "#include <mLib/control.h>"
.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 );
.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
.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
.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
.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
.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 ,
.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
.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
.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
.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 .
.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
.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
.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
.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 .
.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
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
.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 .
.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).
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>
--- /dev/null
+.\" -*-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>
#include "darray.h"
#include "dstr.h"
#include "gprintf.h"
+#include "growbuf.h"
#include "macros.h"
/*----- Tunable constants -------------------------------------------------*/
/* --- 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 --- */
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();
}
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':
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 --- */
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);
/*----- 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@ --- *
*
--- /dev/null
+.\" -*-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>
* which I'm not going to derive here.
*/
-/*----- Header files ------------------------------------------------------*/
-
/*----- Data structures ---------------------------------------------------*/
struct linreg {
--- /dev/null
+.\" -*-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>