/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)alloc.c	2.1 (Chris & John Downey) 7/29/92";
#endif

/***

* program name:
    xvi
* function:
    PD version of UNIX "vi" editor, with extensions.
* module name:
    alloc.c
* module function:
    Various routines dealing with allocation
    and deallocation of data structures.
* history:
    STEVIE - ST Editor for VI Enthusiasts, Version 3.10
    Originally by Tim Thompson (twitch!tjt)
    Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
    Heavily modified by Chris & John Downey

***/

#include "xvi.h"

/*
 * We use a special strategy for the allocation & freeing of Change
 * structures to make these operations as fast as possible (since we
 * have to allocate one for each input character in INSERT & REPLACE
 * states).
 *
 * So we have a linked list of reusable Change structures; freeing a
 * Change means just adding it to this list.
 */
static Change	*chlist = NULL;

/*
 * Free a Change structure. This just means adding it to our list of
 * reusable structures, so a later request for an allocation of a
 * Change can be satisfied very quickly from the front of the list.
 */
void
chfree(ch)
    Change *ch;
{
    ch->c_next = chlist;
    chlist = ch;
}

Change *
challoc()
{
    if (chlist) {
	Change	*ch;

	ch = chlist;
	chlist = chlist->c_next;
	return ch;
    }
    return (Change *) alloc(sizeof (Change));
}

/*
 * We also use a similar strategy for Line structures.
 */
static Line	*lnlist = NULL;

char *
alloc(size)
unsigned size;
{
    char *p;		/* pointer to new storage space */

    while ((p = malloc(size)) == NULL) {
	/*
	 * No more memory in the heap, but we may be able to
	 * satisfy the request by recycling entries in one of
	 * our lists of reusable structures.
	 */
	if (lnlist) {
	    p = (char *) lnlist;
	    lnlist = lnlist->l_next;
	    free(p);
	} else if (chlist) {
	    p = (char *) chlist;
	    chlist = chlist->c_next;
	    free(p);
	} else {
	    /*
	     * No: we're out.
	     */
	    show_error(curwin, "Not enough memory!");
	    break;
	}
    }
    return(p);
}

char *
strsave(string)
const char *string;
{
    char	*space;

    space = alloc((unsigned) strlen(string) + 1);
    if (space != NULL)
	(void) strcpy(space, string);
    return(space);
}

#ifdef MSDOS
#   define MC_SHIFT	(sizeof (int) > 2 ? 5 : 3)
#else
#   define MC_SHIFT	(sizeof (int) > 2 ? 5 : 4)
#endif

/*
 * This is 8 for real mode MS-DOS, 16 for other 16-bit systems,
 * otherwise 32.
 */
#define MEMCHUNK	(1 << MC_SHIFT)

#define MC_MASK		(~0 << MC_SHIFT)

#define MC_ROUNDUP(n)	(((n) + MEMCHUNK - 1) & MC_MASK)

/*
 * Allocate and initialize a new line structure with room for
 * 'nchars' characters.
 */
Line *
newline(nchars)
int	nchars;
{
    register Line	*l;
    char		*ltp;

    if (lnlist == NULL) {
	register unsigned n;

	/*
	 * To avoid memory fragmentation, we try to allocate a
	 * contiguous block of 100 Line structures if we
	 * haven't already got any.
	 *
	 * This means that, for every 100 lines of a file we
	 * read in, there should be a block of Line
	 * structures, which may never be freed, followed by
	 * a large arena for the lines' text and other sundry
	 * dynamically allocated objects, which generally will
	 * be.
	 *
	 * If we can't even get one structure, alloc() should
	 * print an error message. For subsequent ones, we use
	 * malloc() instead because it isn't necessarily a
	 * serious error if we can't get any more space than
	 * we've actually been asked for.
	 */
	if ((lnlist = (Line *) alloc(sizeof(Line))) == NULL) {
	    return(NULL);
	}
	lnlist->l_next = NULL;
	for (n = 99; n != 0; n--) {
	    if ((l = (Line *) malloc(sizeof(Line))) == NULL) {
		break;
	    }
	    l->l_next = lnlist;
	    lnlist = l;
	}
    }

    /*
     * Assertion: lnlist != NULL.
     */
    l = lnlist;
    lnlist = l->l_next;

    /*
     * It is okay for newline() to be called with a 0
     * parameter - but we must never call malloc(0) as
     * this will break on many systems.
     */
    nchars = (nchars == 0 ? MEMCHUNK : MC_ROUNDUP(nchars));
    ltp = alloc((unsigned) nchars);
    if (ltp == NULL) {
	free((char *) l);
	return(NULL);
    }
    ltp[0] = '\0';
    l->l_text = ltp;
    l->l_size = nchars;
    l->l_prev = NULL;
    l->l_next = NULL;

    return(l);
}

/*
 * bufempty() - return TRUE if the buffer is empty
 */
bool_t
bufempty(b)
Buffer	*b;
{
    return(buf1line(b) && b->b_file->l_text[0] == '\0');
}

/*
 * buf1line() - return TRUE if there is only one line
 */
bool_t
buf1line(b)
Buffer	*b;
{
    return(b->b_file->l_next == b->b_lastline);
}

/*
 * endofline() - return TRUE if the given position is at end of line
 *
 * This routine will probably never be called with a position resting
 * on the zero byte, but handle it correctly in case it happens.
 */
bool_t
endofline(p)
Posn	*p;
{
    register char	*endtext = p->p_line->l_text + p->p_index;

    return(*endtext == '\0' || *(endtext + 1) == '\0');
}

/*
 * Adjust the text size of a Line.
 */
bool_t
lnresize(lp, newsize)
Line	*lp;
register unsigned	newsize;
{
    register char	*oldtext;
    register char	*newtext;
    register unsigned	oldsize;

    oldsize = (unsigned) lp->l_size;

    if ((newsize = MC_ROUNDUP(newsize)) == 0)
	return(TRUE);

    if (newsize == oldsize)
	return(TRUE);

    oldtext = lp->l_text;

    if (newsize < oldsize && oldtext != NULL) {
	if ((newtext = realloc(oldtext, newsize)) == NULL) {
	    newtext = oldtext;
	}
    } else {
	if ((newtext = alloc(newsize)) == NULL) {
	    return(FALSE);
	}
	if (oldtext) {
	    (void) strncpy(newtext, oldtext, oldsize - 1);
	    newtext[oldsize - 1] = '\0';
	    free(oldtext);
	}
    }
    lp->l_text = newtext;
    lp->l_size = (int) newsize;
    return(TRUE);
}

/*
 * grow_line(lp, n)
 *	- increase the size of the space allocated for the line by n bytes.
 *
 * This routine returns TRUE immediately if the requested space is available.
 * If not, it attempts to allocate the space and adjust the data structures
 * accordingly, and returns TRUE if this worked.
 * If everything fails it returns FALSE.
 */
bool_t
grow_line(lp, n)
Line	*lp;
int	n;
{
    int		nsize;
    char	*ctp;

    ctp = lp->l_text;
    nsize = (ctp ? strlen(ctp) : 0) + 1 + n;	/* size required */

    if (nsize <= lp->l_size)
	return(TRUE);

    return lnresize(lp, nsize);
}

/*
 * Free up space used by the given list of lines.
 *
 * Note that the Line structures themselves are just added to the list
 * of reusable ones.
 */
void
throw(lineptr)
Line	*lineptr;
{
    if (lineptr != NULL) {
	Line	*newlist;

	newlist = lineptr;
	for (;;) {
	    Line	*nextline;

	    if (lineptr->l_text != NULL)
		free(lineptr->l_text);
	    if ((nextline = lineptr->l_next) == NULL) {
		/*
		 * We've reached the end of this list;
		 * join it on to lnlist ...
		 */
		lineptr->l_next = lnlist;
		/*
		 * ... & point lnlist at the beginning
		 * of this list.
		 */
		lnlist = newlist;
		return;
	    } else {
		lineptr = nextline;
	    }
	}
    }
}
