/* string.c: String-handling routines.
 * This code is not ANSI-conformant, because it allocates memory at the end
 * of String structure and references it with a one-element array. */

#define _POSIX_SOURCE

#include <stdio.h>
#include <string.h>
#include "cmstring.h"
#include "memory.h"

/* Note that we number string elements [0..(len - 1)] internally, while the
 * user sees string elements as numbered [1..len]. */

/* Many implementations of malloc() deal best with blocks eight or sixteen
 * bytes less than a power of two.  MALLOC_DELTA and STRING_STARTING_SIZE take
 * this into account.  We start with a string MALLOC_DELTA bytes less than
 * a power of two.  When we enlarge a string, we double it and add MALLOC_DELTA
 * so that we're still MALLOC_DELTA less than a power of two.  When we
 * allocate, we add in sizeof(String), leaving us 32 bytes short of a power of
 * two, as desired. */

#define MALLOC_DELTA	(sizeof(String) + 32)
#define STARTING_SIZE	(128 - MALLOC_DELTA)

static String *prepare_to_modify(String *string, int len);

String *string_new(int len)
{
    String *new;
    int size;

    size = STARTING_SIZE;
    while (size < len)
	size = size * 2 + MALLOC_DELTA;
    new = (String *) emalloc(sizeof(String) + sizeof(char) * size);
    new->len = len;
    new->size = size;
    new->refs = 1;
    new->reg = NULL;
    return new;
}

String *string_empty(int size)
{
    String *new = string_new(size);

    new->s[0] = 0;
    new->len = 0;
    return new;
}

String *string_from_chars(char *s, int len)
{
    String *new = string_new(len);

    MEMCPY(new->s, s, len);
    new->s[len] = 0;
    return new;
}

String *string_of_char(int c, int len)
{
    String *new = string_new(len);

    memset(new->s, c, len);
    new->s[len] = 0;
    return new;
}

String *string_dup(String *string)
{
    string->refs++;
    return string;
}

String *string_add(String *string, char *s, int len)
{
    string = prepare_to_modify(string, string->len + len);
    MEMCPY(string->s + string->len, s, len);
    string->s[string->len + len] = 0;
    string->len += len;
    return string;
}

String *string_addc(String *string, int c)
{
    string = prepare_to_modify(string, string->len + 1);
    string->s[string->len] = c;
    string->s[++string->len] = 0;
    return string;
}

String *string_parse(char **sptr)
{
    String *str;
    char *s = *sptr, *p;

    str = string_new(0);
    s++;
    while (1) {
	for (p = s; *p && *p != '"' && *p != '\\'; p++);
	str = string_add(str, s, p - s);
	s = p + 1;
	if (!*p || *p == '"')
	    break;
	if (*s)
	    str = string_addc(str, *s++);
    }
    *sptr = s;
    return str;
}

String *string_add_unparsed(String *string, char *s, int len)
{
    int i;

    string = string_addc(string, '"');

    /* Add characters to string, escaping quotes and backslashes. */
    while (1) {
	for (i = 0; i < len && s[i] != '"' && s[i] != '\\'; i++);
	string = string_add(string, s, i);
	if (i < len) {
	    string = string_addc(string, '\\');
	    string = string_addc(string, s[i]);
	    s += i + 1;
	    len -= i + 1;
	} else {
	    break;
	}
    }

    return string_addc(string, '"');
}

String *string_truncate(String *string, int len)
{
    len = (len > string->len) ? string->len : len;
    string = prepare_to_modify(string, len);
    string->s[len] = 0;
    string->len = len;
    return string;
}

String *string_extend(String *string, int len)
{
    return prepare_to_modify(string, len);
}

void string_discard(String *string)
{
    if (!--string->refs) {
	if (string->reg)
	    free(string->reg);
	free(string);
    }
}

static String *prepare_to_modify(String *string, int len)
{
    String *new;

    if (string->refs > 1 || string->size < len) {
	new = string_new(len);
	new->len = string->len;
	MEMCPY(new->s, string->s, string->len + 1);
	new->reg = NULL;
	string_discard(string);
	return new;
    } else {
	if (string->reg) {
	    free(string->reg);
	    string->reg = NULL;
	}
	return string;
    }
}