/* $Id: mstring.c,v 1.11 2024/12/14 16:48:04 tom Exp $ */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include "defs.h"

/* parameters about string length.  HEAD is the starting size and
** HEAD+TAIL should be a power of two */
#define HEAD	24
#define TAIL	8

static char *buf_ptr;
static size_t buf_len;

void
msprintf(struct mstring *s, const char *fmt, ...)
{
    va_list args;
    size_t len;
#ifdef HAVE_VSNPRINTF
    int changed;
#endif

    if (!s || !s->base)
	return;

    if (buf_len == 0)
    {
	buf_ptr = malloc(buf_len = 4096);
    }
    if (buf_ptr == NULL)
    {
	return;
    }

#ifdef HAVE_VSNPRINTF
    do
    {
	va_start(args, fmt);
	len = (size_t)vsnprintf(buf_ptr, buf_len, fmt, args);
	va_end(args);
	if ((changed = (len > buf_len)) != 0)
	{
	    char *new_ptr = realloc(buf_ptr, (buf_len * 3) / 2);
	    if (new_ptr == NULL)
	    {
		free(buf_ptr);
		buf_ptr = NULL;
		return;
	    }
	    buf_ptr = new_ptr;
	}
    }
    while (changed);
#else
    va_start(args, fmt);
    len = (size_t)vsprintf(buf_ptr, fmt, args);
    va_end(args);
    if (len >= buf_len)
	return;
#endif

    if (len > (size_t)(s->end - s->ptr))
    {
	char *new_base;
	size_t cp = (size_t)(s->ptr - s->base);
	size_t cl = (size_t)(s->end - s->base);
	size_t nl = cl;
	while (len > (nl - cp))
	    nl = nl + nl + TAIL;
	if ((new_base = realloc(s->base, nl)))
	{
	    s->base = new_base;
	    s->ptr = s->base + cp;
	    s->end = s->base + nl;
	}
	else
	{
	    free(s->base);
	    s->base = NULL;
	    s->ptr = NULL;
	    s->end = NULL;
	    return;
	}
    }
    memcpy(s->ptr, buf_ptr, len);
    s->ptr += len;
}

int
mputchar(struct mstring *s, int ch)
{
    if (!s || !s->base)
	return ch;
    if (s->ptr == s->end)
    {
	size_t len = (size_t)(s->end - s->base);
	if ((s->base = realloc(s->base, len + len + TAIL)))
	{
	    s->ptr = s->base + len;
	    s->end = s->base + len + len + TAIL;
	}
	else
	{
	    s->ptr = s->end = NULL;
	    return ch;
	}
    }
    *s->ptr++ = (char)ch;
    return ch;
}

struct mstring *
msnew(void)
{
    struct mstring *n = TMALLOC(struct mstring, 1);

    if (n)
    {
	if ((n->base = n->ptr = MALLOC(HEAD)) != NULL)
	{
	    n->end = n->base + HEAD;
	}
	else
	{
	    free(n);
	    n = NULL;
	}
    }
    return n;
}

struct mstring *
msrenew(char *value)
{
    struct mstring *r = NULL;
    if (value != NULL)
    {
	r = msnew();
	r->base = value;
	r->end = value + strlen(value);
	r->ptr = r->end;
    }
    return r;
}

char *
msdone(struct mstring *s)
{
    char *r = NULL;
    if (s)
    {
	mputc(s, 0);
	r = s->base;
	free(s);
    }
    return r;
}

#if defined(YYBTYACC)
/* compare two strings, ignoring whitespace, except between two letters or
** digits (and treat all of these as equal) */
int
strnscmp(const char *a, const char *b)
{
    while (1)
    {
	while (isspace(UCH(*a)))
	    a++;
	while (isspace(UCH(*b)))
	    b++;
	while (*a && *a == *b)
	    a++, b++;
	if (isspace(UCH(*a)))
	{
	    if (isalnum(UCH(a[-1])) && isalnum(UCH(*b)))
		break;
	}
	else if (isspace(UCH(*b)))
	{
	    if (isalnum(UCH(b[-1])) && isalnum(UCH(*a)))
		break;
	}
	else
	    break;
    }
    return *a - *b;
}

unsigned int
strnshash(const char *s)
{
    unsigned int h = 0;

    while (*s)
    {
	if (!isspace(UCH(*s)))
	    h = (h << 5) - h + (unsigned char)*s;
	s++;
    }
    return h;
}
#endif

#ifdef NO_LEAKS
void
mstring_leaks(void)
{
    free(buf_ptr);
    buf_ptr = NULL;
    buf_len = 0;
}
#endif
