/*------------------------------------------------------------------
* String functions.
*
*------------------------------------------------------------------
* A collection of string related functions and utilities:
*
* strbuf_t: an extendable string buffer, useful for incremental
* construction of a string.
* TODO: I am afraid the handling of length in _grow() itself and
* TODO:: its calls is a bit too far on the conservative side.
*------------------------------------------------------------------
*/
/*--------------------------------------------------------------------*/
#include "driver.h"
#include "typedefs.h"
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "strfuns.h"
#include "comm.h"
#include "simulate.h"
#include "svalue.h"
#if 0 /* See below */
#define USES_SVALUE_STRLEN
#include "smalloc.h"
#endif
#include "xalloc.h"
/*--------------------------------------------------------------------*/
void
strbuf_zero (strbuf_t *buf)
/* Initialise the given string buffer <buf>.
*/
{
buf->alloc_len = 0;
buf->length = 0;
buf->buf = NULL;
}
/*--------------------------------------------------------------------*/
void
strbuf_free (strbuf_t *buf)
/* Free the given string buffer <buf>.
* TODO: Not necessary once all strings are counted and with length?
*/
{
if (buf->buf)
xfree(buf->buf);
strbuf_zero(buf);
}
/*--------------------------------------------------------------------*/
static INLINE size_t
strbuf_grow (strbuf_t *buf, size_t len)
/* Extend the stringbuffer <buf> to hold at least <len> more
* bytes (ie. enough for a string of length <len>-1).
*
* Return <len> if all memory could be allocated, or a lower number
* of only part of the required memory is available.
*
* N.B.: be careful with overflows when doing the checks.
*/
{
size_t new_len;
/* Catch some simple situations. */
if (buf->alloc_len >= MAX_STRBUF_LEN)
return 0; /* Truncated */
if (buf->alloc_len - buf->length > len)
return len;
/* Allocate more than we need in anticipation of further adds,
* but not more than we can manage
*/
if (MAX_STRBUF_LEN - buf->length < len * 3)
{
new_len = MAX_STRBUF_LEN;
if (new_len - buf->length < len)
len = new_len - buf->length;
}
else
new_len = buf->length + len * 3;
/* Is this the first allocation? */
if (!buf->buf)
{
buf->buf = xalloc(new_len);
buf->alloc_len = (u_long)new_len;
buf->length = 0;
*(buf->buf) = '\0';
return len;
}
/* Extension of the existing buffer */
/* Using malloc_increment_size() here is tempting, but somehow
* allocates much bigger blocks than needed (or svalue_strlen()
* is lying). TODO: Revisit this later.
* Here is the code for now:
#ifdef MALLOC_smalloc
char * new_buf;
new_buf = malloc_increment_size(buf->buf, new_len - buf->alloc_len);
if (new_buf)
{
buf->alloc_len = (u_long)new_len;
return len;
}
#endif
*/
buf->buf = rexalloc(buf->buf, new_len);
buf->alloc_len = (u_long)new_len;
return len;
} /* strbuf_grow() */
/*--------------------------------------------------------------------*/
void
strbuf_add (strbuf_t *buf, const char * text)
/* Add the <text> to string buffer <buf>.
*/
{
size_t len;
len = strlen(text) + 1;
if (!len)
return;
if (len + buf->length > buf->alloc_len)
len = strbuf_grow(buf, len);
if (len)
{
memcpy(buf->buf+buf->length, text, len);
buf->length += len-1;
buf->buf[buf->length] = '\0';
}
} /* strbuf_add() */
/*--------------------------------------------------------------------*/
void
strbuf_addn (strbuf_t *buf, const char * text, size_t len)
/* Add the <len> characters starting at <text> to string buffer <buf>.
*/
{
if (!len)
return;
len += 1;
if (len + buf->length > buf->alloc_len)
len = strbuf_grow(buf, len);
if (len)
{
len--;
memcpy(buf->buf+buf->length, text, len);
buf->length += len;
buf->buf[buf->length] = '\0';
}
} /* strbuf_addn() */
/*--------------------------------------------------------------------*/
void
strbuf_addc (strbuf_t *buf, char ch)
/* Add the <ch>aracter to string buffer <buf>.
*/
{
size_t len;
len = 2;
if (2 + buf->length > buf->alloc_len)
len = strbuf_grow(buf, 2);
if (len)
{
buf->buf[buf->length] = ch;
buf->length++;
buf->buf[buf->length] = '\0';
}
} /* strbuf_addc() */
/*--------------------------------------------------------------------*/
void
strbuf_addf (strbuf_t *buf, const char * format, ...)
/* Create a string from <format> and the following arguments using
* sprintf() rules, and add the result to the string buffer <buf>.
*/
{
char tmpbuf[4096];
va_list vargs;
tmpbuf[sizeof tmpbuf - 1] = '\0';
va_start(vargs, format);
vsprintf(tmpbuf, format, vargs);
va_end(vargs);
if (tmpbuf[sizeof tmpbuf - 1])
fatal("strbuf_addf: Internal buffer overflow.\n");
strbuf_add(buf, tmpbuf);
} /* strbuf_addf() */
/*--------------------------------------------------------------------*/
void
strbuf_send (strbuf_t *buf)
/* Send the string collected in <buf> out to the current user with
* add_message(), and clear <buf>.
*/
{
if (buf->buf && buf->length)
add_message("%s", buf->buf);
/* Empty the string buffer */
if (buf->buf)
xfree(buf->buf);
buf->buf = NULL;
buf->length = 0;
buf->alloc_len = 0;
} /* strbuf_send() */
/*--------------------------------------------------------------------*/
void
strbuf_store (strbuf_t *buf, svalue_t *svp)
/* Store the string collected in <buf>, which may be the null string "",
* into the empty svalue *<svp>, then clear <buf>.
*/
{
svp->type = T_STRING;
if (buf->buf && buf->length)
{
/* The buffer is most likely allocated longer than necessary,
* and svalue_strlen() gets confused. Therefore create a fresh
* copy.
*/
svp->x.string_type = STRING_MALLOC;
svp->u.string = xalloc(buf->length+1);
memcpy(svp->u.string, buf->buf, buf->length+1);
}
else
{
svp->x.string_type = STRING_VOLATILE;
svp->u.string = "";
}
/* Empty the string buffer */
if (buf->buf)
xfree(buf->buf);
buf->buf = NULL;
buf->length = 0;
buf->alloc_len = 0;
} /* strbuf_store() */
/*--------------------------------------------------------------------*/
static char *
sort_string (const char * in, size_t len, long ** pos)
/* Sort the characters of string <in> (with length <len>) by their numeric
* values and return a newly allocated memory block with the sorted string.
* If <pos> is not NULL, it will be set to a newly allocated memory block
* giving the original positions of the characters in the sorted string.
*
* We use Mergesort to sort the strings.
* TODO: Use Quicksort instead of Mergesort?
*/
{
char * out; /* Result string */
long * outpos; /* Result position array */
char * tmp; /* Temporary string */
long * tmppos; /* Temporary position array */
size_t step;
size_t i, j;
out = xalloc(len+1);
tmp = xalloc(len+1);
if (!out || !tmp)
fatal("(sort_string) Out of memory (2 * %lu bytes) for temporaries.\n"
, (unsigned long) len+1);
out[len] = '\0';
tmp[len] = '\0';
if (pos)
{
outpos = xalloc(len * sizeof(*outpos) + 1);
tmppos = xalloc(len * sizeof(*outpos) + 1);
/* +1 so that smalloc won't complain when given an empty string */
if (!outpos || !tmppos)
fatal("(sort_string) Out of memory (2 * %lu bytes) for positions.\n"
, (unsigned long) len*sizeof(*outpos)+1);
}
else
{
outpos = NULL;
tmppos = NULL;
}
/* First Mergesort pass: comparison of adjacent characters
* and initialisation of the out arrays.
*/
for (i = 0; i < len; i += 2)
{
if (i == len-1)
{
out[i] = in[i];
if (outpos)
outpos[i] = i;
}
else if (in[i] <= in[i+1])
{
out[i] = in[i];
out[i+1] = in[i+1];
if (outpos)
{
outpos[i] = i;
outpos[i+1] = i+1;
}
}
else /* (in[i] > in[i+1]) */
{
out[i] = in[i+1];
out[i+1] = in[i];
if (outpos)
{
outpos[i] = i+1;
outpos[i+1] = i;
}
}
} /* for(initial pass) */
/* Mergesort loop: perform the mergesort passes with increasing steps.
* Invariant: out is the (semi-sorted) data, tmp is the scratchspace.
*/
for (step = 2; step < len; step *= 2)
{
size_t start, dest, left;
/* Exchange out and tmp */
{
char *tmp2;
long *tmp2pos;
tmp2 = out; out = tmp; tmp = tmp2;
if (outpos)
{
tmp2pos = outpos; outpos = tmppos; tmppos = tmp2pos;
}
}
for (start = 0, dest = 0; start <= len; start += 2*step)
{
for ( i = start, j = start+step, left = 2 * step
; left && dest < len
; left--, dest++
)
{
if (i >= start+step
|| i >= len)
{
if (j < len)
{
out[dest] = tmp[j];
if (outpos)
outpos[dest] = tmppos[j];
j++;
}
}
else if (j >= start+2*step
|| j >= len)
{
if (i < len)
{
out[dest] = tmp[i];
if (outpos)
outpos[dest] = tmppos[i];
i++;
}
}
else if (tmp[i] <= tmp[j])
{
out[dest] = tmp[i];
if (outpos)
outpos[dest] = tmppos[i];
i++;
}
else /* (tmp[i] > tmp[i+step]) */
{
out[dest] = tmp[j];
if (outpos)
outpos[dest] = tmppos[j];
j++;
}
} /* for (sort run) */
} /* for (start) */
} /* for(step) */
/* Free the temporary data */
if (tmppos)
xfree(tmppos);
xfree(tmp);
/* Return the result */
if (pos)
*pos = outpos;
return out;
} /* sort_string() */
/*--------------------------------------------------------------------*/
char *
intersect_strings (char * left, char * right, Bool bSubtract)
/* !bSubtract: Intersect string <left> with string <right> and return
* a newly allocated string with all those characters which are in
* both strings.
* bSubtract: Subtract string <right> from string <left> and return
* a newly allocated string with all those characters which are in
* <left> but not in <right>.
* The order of the characters returned is their order of appearance
* in <left>.
*
* Both <left> and <right> are deallocated.
*/
{
size_t len_left, len_right, len_out;
size_t ix_left, ix_right;
long * pos;
CBool * matches;
char * left_in;
char * result;
left_in = left; /* Needed to create the result */
len_left = strlen(left);
len_right = strlen(right);
xallocate(matches, len_left+1, "intersection matches");
/* +1 so that smalloc won't complain when given an empty left string */
for (ix_left = 0; ix_left < len_left; ix_left++)
matches[ix_left] = bSubtract ? MY_TRUE : MY_FALSE;
/* Sort the two strings */
left = sort_string(left, len_left, &pos);
right = sort_string(right, len_right, NULL);
/* Intersect the two strings by mutual comparison.
* Each non-matched character in left gets is pos[] set to -1.
*/
len_out = bSubtract ? len_left : 0;
for ( ix_left = 0, ix_right = 0
; ix_left < len_left && ix_right < len_right
; )
{
if (left[ix_left] < right[ix_right])
ix_left++;
else if (left[ix_left] > right[ix_right])
ix_right++;
else /* left[ix_left] == right[ix_right]) */
{
if (!bSubtract)
{
matches[pos[ix_left]] = MY_TRUE;
len_out++;
}
else
{
matches[pos[ix_left]] = MY_FALSE;
len_out--;
}
ix_left++;
}
}
/* Create the result: copy all flagged characters */
xallocate(result, len_out+1, "intersection result");
result[len_out] = '\0';
for (ix_left = 0, ix_right = 0; ix_left < len_left; ix_left++)
if (matches[ix_left])
result[ix_right++] = left_in[ix_left];
/* Free intermediate results */
xfree(pos);
xfree(matches);
xfree(left);
xfree(right);
return result;
} /* intersect_strings() */
/*--------------------------------------------------------------------*/
char *
xstrncpy (char * dest, const char * src, size_t num)
/* Copy string <src> at address <dest> up to and including the terminating
* 0 or up to size <num>, whichever comes first. Result is <dest>.
*
* In contrast to strncpy(), the copying terminates if a terminating 0
* is found (and copied) in <src> - strncpy() would add additional 0s
* until a total of <num> characters has been written to <dest>.
*/
{
char * p = dest;
while (num-- != 0 && (*p++ = *src++) != '\0') NOOP;
return dest;
} /* xstrncpy() */
/*====================================================================*/