~q3cpma/mus

d94586523cd7936b9c9aa5a082aa2000294ccdb0 — q3cpma 11 months ago c7c5077
Fix genhtab subgit mistake
6 files changed, 869 insertions(+), 1 deletions(-)

D mus_player/genhtab
A mus_player/genhtab/array.h
A mus_player/genhtab/build.sh
A mus_player/genhtab/genhtab.c
A mus_player/genhtab/misc.c
A mus_player/genhtab/misc.h
D mus_player/genhtab => mus_player/genhtab +0 -1
@@ 1,1 0,0 @@
Subproject commit 478aa0435e57449ab959c1276a243fadde9801de

A mus_player/genhtab/array.h => mus_player/genhtab/array.h +124 -0
@@ 0,0 1,124 @@
/* Generic array with length and capacity stored in the first 8 bytes.
 * The array pointer then points to the real pointer (given by malloc)
 * with and offset of 2 * sizeof(size_t) bytes.
 *
 * Inspired by https://github.com/eteran/c-vector, but without all the NULL
 * checking and the PTR_GARRAY functions added */
#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "misc.h"

#define GARRAY_GROWTH_FACTOR 2
#define GARRAY_START_CAPA    16

#define GARRAY_SIZE(a)       ((size_t *)(a))[-2]
#define GARRAY_CAPA(a)       ((size_t *)(a))[-1]
#define GARRAY_PTR(a)        (((size_t *)(a)) - 2)
#define GARRAY_ALLOC_SIZE(a) (sizeof(size_t) * 2 + GARRAY_CAPA(a) * sizeof(*(a)))

#define GARRAY_INIT(a)													\
	do																	\
	{																	\
	    (a) = xmalloc(sizeof(size_t) * 2 + GARRAY_START_CAPA * sizeof(*(a))); \
	    (a) = (void *) (((size_t *)(a)) + 2);							\
	    GARRAY_SIZE(a) = 0;												\
	    GARRAY_CAPA(a) = GARRAY_START_CAPA;								\
	}																	\
	while (0)

#define GARRAY_FREE(a)							\
	do                                          \
	{                                           \
	    if (a)                                  \
	        free(GARRAY_PTR(a));				\
	}                                           \
	while (0)

#define GARRAY_GROW(a)							\
	do											\
	{											\
	    (a) = xreallocarray(					\
			GARRAY_PTR(a),						\
			GARRAY_ALLOC_SIZE(a),				\
	        GARRAY_GROWTH_FACTOR				\
		);										\
	    (a) = (void *) (((size_t *)(a)) + 2);	\
	    GARRAY_CAPA(a) *= GARRAY_GROWTH_FACTOR;	\
	}											\
	while (0)

#define GARRAY_APPEND(a, e)						\
	do                                          \
	{                                           \
	    if (GARRAY_SIZE(a) == GARRAY_CAPA(a))   \
	        GARRAY_GROW(a);						\
	    (a)[GARRAY_SIZE(a)] = e;				\
	    ++GARRAY_SIZE(a);						\
	}                                           \
	while (0)

#define GARRAY_EXTEND(a, b, bnmemb)							\
	do														\
	{														\
	    while (GARRAY_CAPA(a) - GARRAY_SIZE(a) < bnmemb)	\
	        GARRAY_GROW(a);									\
	    memcpy(a + GARRAY_SIZE(a), b, bnmemb * sizeof(*b));	\
	    GARRAY_SIZE(a) += bnmemb;							\
	}														\
	while (0)

#define GARRAY_POP(a)							\
	do                                          \
	{                                           \
	    if (GARRAY_SIZE(a))						\
	        --GARRAY_SIZE(a);					\
	}                                           \
	while (0)

#define GARRAY_PRINT(stream, fmt, a)									\
	do																	\
	{																	\
	    if (GARRAY_SIZE(a))												\
	    {																\
	        for (size_t GENSYM(0) = 0; GENSYM(0) < GARRAY_SIZE(a) - 1; ++GENSYM(0)) \
	            fprintf(stream, fmt ", ", (a)[GENSYM(0)]);				\
	        fprintf(stream, fmt "\n", (a)[GARRAY_SIZE(a) - 1]);			\
	    }																\
	}																	\
	while (0)

#define PTR_GARRAY_POP(a, free)					\
	do                                          \
	{                                           \
	    if (GARRAY_SIZE(a))						\
	        free((a)[--GARRAY_SIZE(a)]);		\
	}                                           \
	while (0)

#define PTR_GARRAY_PRINT(stream, print, a)								\
	do																	\
	{																	\
	    if (GARRAY_SIZE(a))												\
	    {																\
	        for (size_t GENSYM(0) = 0; GENSYM(0) < GARRAY_SIZE(a) - 1; ++GENSYM(0)) \
	        {															\
	            print(stream, (a)[GENSYM(0)]);							\
	            fprintf(stream, ", ");									\
	        }															\
	        print(stream, (a)[GARRAY_SIZE(a) - 1]);						\
	        fprintf(stream, "\n");										\
	    }																\
	}																	\
	while (0)

#define PTR_GARRAY_FREE(a, free)										\
	do																	\
	{																	\
		for (size_t GENSYM(0) = 0; GENSYM(0) < GARRAY_SIZE(a) - 1; ++GENSYM(0)) \
	        free((a)[GENSYM(0)]);										\
	}																	\
	while (0)

A mus_player/genhtab/build.sh => mus_player/genhtab/build.sh +32 -0
@@ 0,0 1,32 @@
#!/bin/sh
set -eu
cd -- "$(dirname -- "$0")"
self=./$(basename -- "$0")
. ../../build_util.sh

CC=c99
CFLAGS=-O1
CPPFLAGS=-D_DEFAULT_SOURCE
LDFLAGS=-s
JOBS=1
BIN=genhtab
append_cppflag \
	-DPROG_NAME="$(dquote dummy 2)" \
	-DPROG_VERSION="$(dquote dummy 2)"
SRC=$(pecho *.c)

if [ $# -eq 1 ]
then
	case "$(tolower "$1")" in
		clean)
			cclean
			;;
		install|uninstall|help) ;;
		*)
			die "$1: unknown action"
			;;
	esac
	exit
fi

pb_make

A mus_player/genhtab/genhtab.c => mus_player/genhtab/genhtab.c +413 -0
@@ 0,0 1,413 @@
#include <ctype.h>
#include <getopt.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#include "array.h"
#include "misc.h"


struct htab
{
	uint32_t len;
	struct item
	{
		uint32_t keylen;
		char *key, *tag;
	} **buckets;
};


static uint32_t fnv1a(const uint8_t *k, size_t len)
{
	uint_fast32_t h = 2166136261u;
	for (size_t i = 0; i < len; ++i)
	{
		h = (h ^ k[i]) * 16777619u;
	}
	return h & 0xffffffff;
}

static const struct item * bucket_search(struct item it, struct item *bucket)
{
	for (size_t i = 0; i < GARRAY_SIZE(bucket); ++i)
	{
		if (it.keylen == bucket[i].keylen && memcmp(it.key, bucket[i].key, it.keylen) == 0)
		{
			return bucket + i;
		}
	}
	return NULL;
}

NORETURN void usage(int exit_status)
{
	printf(
		"NAME\n"
		"    %s - Generate static hash tables\n"
		"\n"
		"SYNOPSIS\n"
		"    %s [OPTIONS]\n"
		"\n"
		"DESCRIPTION\n"
		"    Generate a static hash table as a C99 header and source file pair from a\n"
		"    key/tag list specification.\n"
		"    NOTE: if non ASCII keys are used, they're considered UTF-8 encoded and the\n"
		"    produced table uses C11 u8\"...\" string literals.\n"
		"\n"
		"    That spec is read on stdin as a newline separated list of entries made of\n"
		"    the key (string), one or more delimiter characters (defaults to TAB) and the\n"
		"    tag (type defaults to int).\n"
		"    If the option -l is used, the first line is an exception and contains only\n"
		"    the tag that'll be returned for keys not in the table.\n"
		"\n"
		"    By default, the output files are named htab.c and htab.h. The header\n"
		"    includes a function named htab_search taking a key together with its\n"
		"    length and returning the associated tag.\n"
		"\n"
		"OPTIONS\n"
		"    -h\n"
		"        Print this help message and exit.\n"
		"\n"
		"    -d CHAR\n"
		"        Set the key/tag spec delimiter to CHAR.\n"
		"\n"
		"    -f FUNC_NAME\n"
		"        Change the search function name from htab_search to FUNC_NAME.\n"
		"\n"
		"    -i HEADER\n"
		"        Add HEADER as an additional local #include to the header file. Mainly\n"
		"        to allow the use of a user defined type for tag values.\n"
		"\n"
		"    -l\n"
		"        Literal mode, instead of returning a pointer to the value (mainly for\n"
		"        NULL to be used as special value), returns literals of the chosen type\n"
		"        with the first line of the spec as \"not found\" value.\n"
		"\n"
		"    -m MULT\n"
		"        Use MULT as a floating point multiplier instead of 0.75 when setting the\n"
		"        table bucket number to `mult * key_number`.\n"
		"\n"
		"    -o OUTPUT\n"
		"        Set output to OUTPUT.c and OUTPUT.h instead of htab.c and htab.h.\n"
		"\n"
		"    -t TAG_TYPE\n"
		"        Set the tag type to TAG_TYPE instead of int.\n"
		"\n"
		"    -v\n"
		"        Print the version and exit.\n"
		"\n"
		"    -w\n"
		"        Overwrite any output file without complaining.\n"
		"\n",
		PROG_NAME, PROG_NAME);
	exit(exit_status);
}


int main(int argc, char **argv)
{
	signals_nointerrupt();

	const char *outpath = "htab";
	const char *tagtype = "int";
	const char *searchfun = "htab_search";
	const char **addinclude = NULL;
	GARRAY_INIT(addinclude);
	char delim = '\t';
	float bucketnum_mult = 0.75;
	bool literal_mode = false;
	bool overwrite = false;

	for (int opt; (opt = getopt(argc, argv, "hd:f:i:lm:o:t:vw")) != -1; )
    {
        switch (opt)
		{
			case 'h':
				usage(EXIT_SUCCESS);
				break;

			case 'd':
				delim = optarg[0];
				break;

			case 'f':
				searchfun = optarg;
				break;

			case 'i':
				GARRAY_APPEND(addinclude, optarg);
				break;

			case 'l':
				literal_mode = true;
				break;

			case 'm':
				bucketnum_mult = xstrtof(optarg);
				break;

			case 'o':
				outpath = optarg;
				break;

			case 't':
				tagtype = optarg;
				break;

			case 'v':
                puts(PROG_VERSION);
                exit(EXIT_SUCCESS);

			case 'w':
				overwrite = true;
				break;

			default:
				exit(EXIT_FAILURE);
		}
	}

	size_t dummy = 0;
	char **key, **tag, *line = NULL, *notfoundtag = NULL;
	uint32_t *keylen;
	size_t maxkeylen = 0;
	GARRAY_INIT(key);
	GARRAY_INIT(tag);
	GARRAY_INIT(keylen);

	for (ssize_t read; (read = xgetline(&line, &dummy, stdin)) != -1;
		line = NULL, dummy = 0)
	{
		if (literal_mode && notfoundtag == NULL)
		{
			notfoundtag = line;
			continue;
		}
		char *p = strchr(line, delim);
		if (p == NULL)
		{
			die("%s: malformed entry; no delimiter found", line);
		}
		size_t len = p - line;
		for (; *p == delim; ++p)
		{
			*p = '\0';
		}

		if (len > UINT32_MAX)
		{
			die("Key is too long, length field is an uint32_t");
		}
		if (len > maxkeylen)
		{
			maxkeylen = len;
		}
		GARRAY_APPEND(key, line);
		GARRAY_APPEND(keylen, len);

		if (literal_mode && !strcmp(p, notfoundtag))
		{
			die("%s: invalid tag, already reserved for unknown keys", p);
		}
		GARRAY_APPEND(tag, p);
	}
	free(line);

	if (GARRAY_SIZE(key) == 0)
	{
		die("No keys given");
	}

	struct htab ht;
	ht.len = (uint32_t)1 << (log2_u32(GARRAY_SIZE(key) * bucketnum_mult) + 1);
	ht.buckets = xcalloc(ht.len, sizeof(struct item *));

	for (size_t i = 0; i < GARRAY_SIZE(key); ++i)
	{
		struct item it = {keylen[i], key[i], tag[i]};
		uint32_t h = fnv1a((uint8_t *)key[i], keylen[i]) & (ht.len - 1);
		if (ht.buckets[h])
		{
			if (bucket_search(it, ht.buckets[h]))
			{
				die("%s: duplicate key", key[i]);
			}
		}
		else
		{
			GARRAY_INIT(ht.buckets[h]);
		}
		GARRAY_APPEND(ht.buckets[h], it);
	}


	size_t outpathlen = strlen(outpath);
	char *pathbuf = xmalloc(outpathlen + 2 + 1);
	memcpy(pathbuf, outpath, outpathlen);
	memcpy(pathbuf + outpathlen, ".c", 3);
	if (!overwrite && !access(pathbuf, F_OK))
	{
		die("%s: file already exists", pathbuf);
	}
	FILE *source = xfopen(pathbuf, "w");
	memcpy(pathbuf + outpathlen, ".h", 3);
	if (!overwrite && !access(pathbuf, F_OK))
	{
		die("%s: file already exists", pathbuf);
	}

	fprintf(source,
		"#include <string.h>\n"
		"\n"
		"#include \"%s\"\n"
		"\n"
		"static const struct\n"
		"{\n"
		"    uint32_t len;\n"
		"    uint32_t maxkeylen;\n"
		"    struct bucket\n"
		"    {\n"
		"        uint32_t len;\n"
		"        struct item\n"
		"        {\n"
		"            char *key;\n"
		"            uint32_t keylen;\n"
		"            %s tag;\n"
		"        } *items;\n"
		"    } *buckets;\n"
		"} htab =\n"
		"{\n"
		"    %" PRIu32 ",\n"
		"    %" PRIu32 ",\n"
		"    (struct bucket [])\n"
		"    {\n",
		pathbuf, tagtype, ht.len, (uint32_t)maxkeylen);

	for (size_t i = 0; i < ht.len; ++i)
	{
		if (ht.buckets[i] == NULL)
		{
			fprintf(source,
				"        {\n"
				"            0,\n"
				"            NULL\n"
				"        },\n");
		}
		else
		{
			fprintf(source,
				"        {\n"
				"            %" PRIu32 ",\n"
				"            (struct item [])\n"
				"            {\n",
				(uint32_t)GARRAY_SIZE(ht.buckets[i]));
			for (size_t j = 0; j < GARRAY_SIZE(ht.buckets[i]); ++j)
			{
				fprintf(source,
					"                {");
				fprintf_cstrlit(source,
					ht.buckets[i][j].key, ht.buckets[i][j].keylen);
				fprintf(source,
					", %" PRIu32 ", %s},\n",
					ht.buckets[i][j].keylen, ht.buckets[i][j].tag);
			}
			fprintf(source,
				"            }\n"
				"        },\n");
		}
	}

	fprintf(source,
		"    }\n"
		"};\n"
		"\n");

	char *rettype;
	char *notfoundret;
	if (literal_mode)
	{
		rettype = (char *)tagtype;
		pasprintf(&notfoundret, "(%s)%s", tagtype, notfoundtag);
	}
	else
	{
		pasprintf(&rettype, "const %s *", tagtype);
		notfoundret = "NULL";
	}

	fprintf(source,
		"static uint32_t fnv1a(const uint8_t *key, size_t len)\n"
		"{\n"
		"    uint_fast32_t h = 2166136261u;\n"
		"    for (size_t i = 0; i < len; ++i)\n"
		"    {\n"
		"        h = (h ^ key[i]) * 16777619u;\n"
		"    }\n"
		"    return h & 0xffffffff;\n"
		"}\n"
		"\n"
		"%s %s(const char *key, size_t len)\n"
		"{\n"
		"    if (len > htab.maxkeylen)\n"
		"    {\n"
		"        return %s;\n"
		"    }\n"
		"    uint32_t h = fnv1a((uint8_t *)key, len) & (htab.len - 1);\n"
		"    for (size_t i = 0; i < htab.buckets[h].len; ++i)\n"
		"    {\n"
		"        if (len == htab.buckets[h].items[i].keylen &&\n"
		"            memcmp(key, htab.buckets[h].items[i].key, len) == 0)\n"
		"        {\n"
		"            return %shtab.buckets[h].items[i].tag;\n"
		"        }\n"
		"    }\n"
		"    return %s;\n"
		"}\n",
		rettype, searchfun, notfoundret, (literal_mode ? "" : "&"), notfoundret);
	xfclose(source);

	char *header_guard;
	pasprintf(&header_guard, "%s_GUARD", searchfun);
	for (size_t i = 0; i < strlen(searchfun); ++i)
	{
		header_guard[i] = toupper(searchfun[i]);
	}
	FILE *header = xfopen(pathbuf, "w");
	fprintf(header,
		"#ifndef %s\n"
		"#define %s\n"
		"\n"
		"#include <stddef.h>\n"
		"#include <stdint.h>\n"
		"\n",
		header_guard,
		header_guard);
	for (size_t i = 0; i < GARRAY_SIZE(addinclude); ++i)
	{
		fprintf(header, "#include \"%s\"\n", addinclude[i]);
	}
	fprintf(header,
		"\n"
		"%s %s(const char *key, size_t len);\n"
		"\n"
		"#endif\n",
		rettype, searchfun);
	xfclose(header);

	free(literal_mode ? notfoundret : rettype);
	GARRAY_FREE(addinclude);
	PTR_GARRAY_FREE(key, free);
	GARRAY_FREE(tag);
	GARRAY_FREE(keylen);

	for (size_t i = 0; i < ht.len; ++i)
	{
		GARRAY_FREE(ht.buckets[i]);
	}
	free(ht.buckets);
	free(pathbuf);
}

A mus_player/genhtab/misc.c => mus_player/genhtab/misc.c +218 -0
@@ 0,0 1,218 @@
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "misc.h"


void die(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	fputc('\n', stderr);
	va_end(args);
	exit(EXIT_FAILURE);
}

ssize_t xgetdelim(char **lineptr, size_t *n, int delim, FILE *stream)
{
	errno = 0;
	ssize_t read = getdelim(lineptr, n, delim, stream);

	if (read != -1)
	{
		if ((*lineptr)[read - 1] == delim)
		{
			(*lineptr)[--read] = '\0';
		}
	}
	else if (errno)
	{
		die("getdelim: %s", strerror(errno));
	}
	return read;
}

void signals_nointerrupt(void)
{
	static int sigs[] = {SIGCHLD, SIGCONT, SIGTSTP, SIGURG, SIGWINCH};
	struct sigaction cur;

	for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); ++i)
	{
		sigaction(sigs[i], NULL, &cur);
		cur.sa_flags |= SA_RESTART;
		if (sigaction(sigs[i], &cur, NULL))
		{
			die("signal %d: %s", sigs[i], strerror(errno));
		}
	}
}

uint32_t log2_u32(uint32_t x)
{
#define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
	static const uint8_t log2lut[256] =
	{
		0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
		LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
		LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
	};
#undef LT
	const uint32_t mask[] =  {0xffff0000, 0xff00};
	const uint32_t shift[] = {16,         8};

	uint32_t log = 0;
	for (uint8_t i = 0; i < 2; ++i)
	{
		if (x & mask[i])
		{
			x >>= shift[i];
			log |= shift[i];
		}
	}
	return log + log2lut[x];
}

void fprintf_cstrlit(FILE *stream, const char *str, size_t len)
{
	static char buf[BUFSIZ];
	size_t bufcapa = BUFSIZ;
	char *p = buf;
	bool utf8 = false;

	for (size_t i = 0; i < len; ++i)
	{
		if (iscntrl(str[i]) || str[i] == '"' || str[i] == '\\')
		{
			// snprintf can only fail if n > INT_MAX (cf POSIX)
			size_t ret = snprintf(p, bufcapa, "\\x%02x", (uint8_t)str[i]);
			p += ret;
			bufcapa -= ret;
		}
		else
		{
			if (str[i] > '~')
			{
				utf8 = true;
			}
			*p++ = str[i];
			--bufcapa;
		}
	}
	*p = '\0';
	fprintf(stream, "%s\"%s\"", utf8 ? "u8" : "", buf);
}

unsigned long xstrtoul(const char *str, int base)
{
	char *end;
	unsigned long ret = strtoul(str, &end, base);
	if (*str == '\0' || *end != '\0' || (errno == ERANGE && ret == ULONG_MAX))
	{
		die("%s: invalid base%d unsigned long number", str, base);
	}
	return ret;
}

int pasprintf(char **s, const char *fmt, ...)
{
	va_list args[2];
	va_start(args[0], fmt);
	va_copy(args[1], args[0]);
	size_t len = vsnprintf(NULL, 0, fmt, args[0]) + 1;
	*s = xmalloc(len);
	int ret = vsprintf(*s, fmt, args[1]);
	va_end(args[0]);
	va_end(args[1]);
	return ret;
}

float xstrtof(const char *str)
{
    if (*str == '\0')
	{
        die("empty string argument");
	}
    char *end;
    const float ret = strtof(str, &end);
    if (*end != '\0')
	{
        die("%s: trailing text found during conversion to float", str);
	}
	if (errno == EINVAL)
	{
		die("%s: %s", str, strerror(errno));
	}
    if (errno == ERANGE)
    {
        die("%s: float conversion %s", str,
            ret == HUGE_VAL || ret == -HUGE_VAL ? "overflow" : "underflow");
    }
    return ret;
}

FILE * xfopen(const char *path, const char *mode)
{
	FILE *f = fopen(path, mode);
	if (f == NULL)
	{
		die("fopen(%s, %s): %s", path, mode, strerror(errno));
	}
	return f;
}

void xfclose(FILE *stream)
{
	if (fclose(stream) == EOF)
	{
		die("fclose: %s", strerror(errno));
	}
}

void * xmalloc(size_t size)
{
    void *p = malloc(size);
	if (p == NULL)
	{
		die("malloc(%zu): %s", size, strerror(errno));
	}
    return p;
}

void * xcalloc(size_t nmemb, size_t size)
{
    void *p = calloc(nmemb, size);
	if (p == NULL)
	{
		die("calloc(%zu, %zu): %s", nmemb, size, strerror(errno));
	}
    return p;
}

void * xrealloc(void *ptr, size_t size)
{
	void *p = realloc(ptr, size);
	if (p == NULL)
	{
		die("realloc(%p, %zu): %s", ptr, size, strerror(errno));
	}
    return p;
}

void * xreallocarray(void *ptr, size_t nmemb, size_t size)
{
	if (nmemb > SIZE_MAX / size)
	{
		die("reallocarray(%p, %zu, %zu): overflow detected", ptr, nmemb, size);
	}
    return xrealloc(ptr, nmemb * size);
}

A mus_player/genhtab/misc.h => mus_player/genhtab/misc.h +82 -0
@@ 0,0 1,82 @@
#pragma once

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#if __STDC_VERSION__ >= 201112L
#	define NORETURN _Noreturn
#else
#	define NORETURN
#endif

// Use the format(printf) attribute only if using GCC/clang
#if defined(__GNUC__) || defined(__clang__)
#	define ATTR_FMT_PRINTF(fmt_index, chk_index) \
		__attribute__ ((format (printf, fmt_index, chk_index)))
#else
#	define ATTR_FMT_PRINTF(fmt_index, chk_index)
#endif

#if defined(__GNUC__) || defined(__clang__)
#	define ATTR_FMT_SCANF(fmt_index, chk_index) \
		__attribute__ ((format (scanf, fmt_index, chk_index)))
#else
#	define ATTR_FMT_SCANF(fmt_index, chk_index)
#endif

#define ARRAY_SIZE(a) (sizeof(a) / sizeof (a)[0])

// gensym implementation (generate a pseudo-unique variable name in a macro)
// could use a reserved name syntax (leading __ or _[A-Z]) but not needed
#define GENSYM__(line, x) gensym_ ## l ## line ## _ ## x
#define GENSYM_(line, x) GENSYM__(line, x)
#define GENSYM(x) GENSYM_(__LINE__, x)

#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)

#if defined(__GNUC__) || defined(__clang__)
#	define LIKELY(x)   (__builtin_expect(!!(x), 1))
#	define UNLIKELY(x) (__builtin_expect(!!(x), 0))
#else
#	define LIKELY(x)   (x)
#	define UNLIKELY(x) (x)
#endif


/* printf a message to stderr followed by a newline before exit(1) */
NORETURN void die(const char *fmt, ...) ATTR_FMT_PRINTF(1, 2);

/* getdelim() wrapper removing the delimiter if found at the end of the line
   and dying when encountering an error */
ssize_t xgetdelim(char **lineptr, size_t *n, int delim, FILE *stream);

static inline ssize_t xgetline(char **lineptr, size_t *n, FILE *stream)
{
	return xgetdelim(lineptr, n, '\n', stream);
}

/* Set SA_RESTART for all signals not having term/core as default handler */
void signals_nointerrupt(void);

/* integer log2 for u32; return 0 when x is 0 */
uint32_t log2_u32(uint32_t x);

/* Print a string as C string literal; uses C11's u8 notation if any byte > 127
 * is encountered (e.g. u8"café") */
void fprintf_cstrlit(FILE *stream, const char *str, size_t len);

/* Portable asprintf */
int pasprintf(char **s, const char *fmt, ...) ATTR_FMT_PRINTF(2, 3);

/* Wrappers around standard functions that die() in case of error */
float         xstrtof(const char *str);
unsigned long xstrtoul(const char *str, int base);
FILE *        xfopen(const char *path, const char *mode);
void          xfclose(FILE *stream);
void *        xmalloc(size_t size);
void *        xcalloc(size_t nmemb, size_t size);
void *        xrealloc(void *ptr, size_t size);
void *        xreallocarray(void *ptr, size_t nmemb, size_t size);