#define REALHEAP /* Don't use private heap for now */
/*

     THE BIG PICTURE:
     There are three distinct needs for memory in this program:

     1) Temporary storage for the parser - strings matched, and the
        parse-tree itself.  This floats up and down as the parse
        tries different routes, like a stack.  We implement this with
        a single block of memory from which we allocate pointers
        in a stack-line manner.  The alloc procedure should check that
        it's memory never crosses the downward-growing heap from below;
        also the free procedure should check for consistency.  (Freeing
        in this case means resetting the heap pointer to somewhere lower
        down, in the Pascal mark/release model)
        
        Procedures: stackmalloc  stackmark  stackrelease
                    (stackcalloc, stackrealloc, and stackfree are not used)
        
     2) Temporary storage for results of evaluation functions (strings)
        These can safely all be freed between statement parses.
        This data can either be off a real heap - as everything is
        freed explicitly - or off a second pseudo-heap like the one
        above, where everything is freed after the last use of the
        data has been flagged as free by the a call to a primitive
        free() procedure which simply keeps count of strings allocated/
        freed rather than noting where they are.  The first implentation
        of this area used the real heap; the second uses our big block
        of ram, starting at the opposite end from the parser's stack.
        If any of the strings returned from this storage area need
        to be kept permanently, they must be copied to the real heap first.
        heapfree should make a cheap test to check that it's parameter
        is in it's own private heap area.  Also, that the downgrowing
        heap never crosses the upgrowing stack.

        Procedures: heapalloc  heapjoin  heapfree

     3) Stuff that needs to be held permanently, off the normal heap.
        This data may or may not be accounted for with the <mnemosyne.h>
        debugging package.  If it is, the major block malloc for the
        two areas above should *not* be accounted, so that the package
        will fault any frees of the wrong sort of heap data.

        Procedures: malloc  free

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

/* Nothing in this file should use mnemosyn */
#ifdef MEMDEBUG
#include "mnemosyn.h" /* TEMP */
#endif

#include "log.h"

/* The stack requirements for line-based programs are remarkably
   low - I've run most tests in around 10K as reported by stack_dumpstats() */

//#if defined(DOS386) || defined(__GCC__) || defined(__riscos) || defined (__UNIX__)
/* This has to be tweaked according to 'high water mark' */
#define MAX_HEAP (1024L*1024L)
//#else
//#define MAX_HEAP (15L*1024L)
//#endif

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1 /* For some reason, not picked up by GCC */
#endif

/* Changed these from static array to heap pointers -- *might* even now
   work in 8086 modes if suitably small... */
static char *heap = NULL, *next, *last, *bot;
long lowheap = MAX_HEAP; /* 'empty' pointer */
static long mallcount = 0L;
static void domalloc(void)
{
  if (heap != NULL) {
    fprintf(stderr,
      "Watch it buddy!  You've already initialised the stackheap!\n");
  }
  next = heap = (char *)malloc(MAX_HEAP);
  bot = &heap[lowheap = MAX_HEAP];  /* exclusive */
  if (heap == NULL) {
    LogFatal("mmalloc", __FILE__, -1,
      "not enough memory to run parser (%ld wanted)", MAX_HEAP);
    exit(EXIT_FAILURE);
  }
  last = &heap[MAX_HEAP]; /* Exclusive pointer */
}

#include "log.h"

long highstack = -1L;

void stack_dumpstats(void)
{
  FILE *f;
  if (highstack == -1L) highstack = 0L;
  f = fopen("mnem.stk", "w");
  if (f == NULL) f = stderr;
  fprintf(f, "Stack: %ld\n", (long)(next-heap));
  fprintf(f, "Stack high-water mark: %ld\n", highstack);
  if (f != stderr) {
    fprintf(stderr, "Stack usage information written to mnem.stk\n");
    fclose(f);
  }
  free(heap);
}

void *stackmalloc(size_t size)
{
  void *this;
  /*
   * allocates space for an object whose size is specified by 'size' and whose
   * value is indeterminate.
   * Returns: either a null pointer or a pointer to the allocated space.
   */
  if (highstack == -1L) { highstack = 0L; atexit(stack_dumpstats); domalloc(); }
  size = (size + 3) & (~3);
  if (((long)next)+(long)size > (long)last) {
     LogFatal(__FILE__, __FILE__, 0L,
       "Unexpectedly out of heap space! - cur limit is %ld [size=%u]\n",
       MAX_HEAP, size);
     exit(EXIT_FAILURE);
     /*return(NULL);*/
  }
  this = next; next = next+size; if (next - heap > highstack) highstack = next-heap;
  /*fprintf(stderr, "Malloc(%d) -> %p\n", size, this);*/
  return(this);
}

void *stackcalloc(size_t nmemb, size_t size)
{
  void *this;
  /*
   * allocates space for an array of nmemb objects, each of whose size is
   * 'size'. The space is initialised to all bits zero.
   * Returns: either a null pointer or a pointer to the allocated space.
   */
  if (highstack == -1L) { highstack = 0L; atexit(stack_dumpstats); domalloc(); }
  size = (size + 3) & (~3);
  if ((long)next+((long)size*(long)nmemb) > (long)last) {
     LogFatal(__FILE__, __FILE__, 0L,
     "Unexpectedly out of heap space! - cur limit is %ld\n", MAX_HEAP);
     exit(EXIT_FAILURE);
     /*return(NULL);*/
  }
  this = next; next = next+(size*nmemb); if (next - heap > highstack) highstack = next-heap;
  memset(this, '\0', size*nmemb);
  fprintf(stderr, "Calloc(%ld, %ld) -> %p\n", (long)nmemb, (long)size, this);
  return(this);
}

void stackfree(void *ptr)
{
   /*
    * causes the space pointed to by ptr to be deallocated (i.e., made
    * available for further allocation). If ptr is a null pointer, no action
    * occurs. Otherwise, if ptr does not match a pointer earlier returned by
    * calloc, malloc or realloc or if the space has been deallocated by a call
    * to free or realloc, the behaviour is undefined.
    */
  /*fprintf(stderr, "Free(%p)\n", ptr);*/
  if (highstack == -1L) {
    fprintf(stderr, "You can't free if you've never allocked, dummy!\n");
    highstack = 0L; atexit(stack_dumpstats);
  }
}

void *stackrealloc(void *ptr, size_t size)
{
   /*
    * changes the size of the object pointed to by ptr to the size specified by
    * size. The contents of the object shall be unchanged up to the lesser of
    * the new and old sizes. If the new size is larger, the value of the newly
    * allocated portion of the object is indeterminate. If ptr is a null
    * pointer, the realloc function behaves like a call to malloc for the
    * specified size. Otherwise, if ptr does not match a pointer earlier
    * returned by calloc, malloc or realloc, or if the space has been
    * deallocated by a call to free or realloc, the behaviour is undefined.
    * If the space cannot be allocated, the object pointed to by ptr is
    * unchanged. If size is zero and ptr is not a null pointer, the object it
    * points to is freed.
    * Returns: either a null pointer or a pointer to the possibly moved
    *          allocated space.
    */
  if (highstack == -1L) {
    fprintf(stderr, "You can't realloc if you've never allocked, dummy!\n");
    highstack = 0L; atexit(stack_dumpstats);
  }
  size = (size + 3) & (~3);
  fprintf(stderr, "Realloc not yet implemented!\n");
  return(NULL);
}

void *stackmark(void)
{
  if (highstack == -1L) { highstack = 0L; atexit(stack_dumpstats); domalloc(); }
  return(next);
}

void stackrelease(void *where)
{
  if (highstack == -1L) {
    fprintf(stderr, "You can't release if you've never marked, dummy!\n");
    highstack = 0L; atexit(stack_dumpstats);
  }
  next = where;
}

/* This *was* 'real' heap stuff - about to change it so that
   space is used off the high end of private memory.
 */


char *topheapmalloc(size_t len)
{
/* Malloc itself shouldn't do NULL checking.  Let the caller do that. */
#ifdef REALHEAP
  char *ptr;
  ptr = malloc(len);
  return(ptr);
#else
  if (lowheap - len <= highstack) return(NULL);
  lowheap -= len;
  bot -= len;
  if (bot != &heap[lowheap]) {
    fprintf(stderr, "internal error in topheap - lowheap=%ld MAX_HEAP=%ld bot=%p last=%p\n",
      lowheap, MAX_HEAP, bot, last);
    return(NULL);
  }
  mallcount += 1L;
  /*fprintf(stderr, "topheapmalloc: %d at &heap[%ld]\n", len, lowheap);*/
  return(bot);
#endif
}

char *tempheapmalloc(int size)
{
   char *s;
   s = topheapmalloc(size);
   if (s == NULL) {
      LogFatal("", "", -1,
        "malloc fails in tempheapmalloc() - not enough room\n");
      exit(EXIT_FAILURE);
   }
   *s = '\0';
   return(s);
}

char *tempheapcopyof(char *text)
{
   char *s;
   s = topheapmalloc((size_t)(strlen(text)+1));
   if (s == NULL) {
      LogFatal("", "", -1,
        "malloc fails in tempheapcopyof() - not enough room\n");
      exit(EXIT_FAILURE);
   }
   strcpy(s, text);
   return(s);
}

/* This is obsoleted by formatf() */
char *tempheapjoin(char *st1, char *st2)
{
  char *s;
  s = topheapmalloc(strlen(st1)+strlen(st2)+1);
  if (s == NULL) {
    LogFatal("tempheapjoin", "", -1L, "no more heap space available\n");
    exit(EXIT_FAILURE);
  }
  strcpy(s, st1);
  strcat(s, st2);
  return(s);
}

char *tempheapcopy(char *st)
{
  char *s;
  s = topheapmalloc(strlen(st)+1);
  if (s == NULL) {
    LogFatal("tempheapcopy", "", -1L, "no more heap space available\n");
    exit(EXIT_FAILURE);
  } else {
    strcpy(s, st);
  }
  return(s);
}

void tempheapfree(char *s)
{
  if (s == NULL) {
    fprintf(stderr, "NULL pointer detected by tempheapfree!\n");
    exit(EXIT_FAILURE);
  }
#ifdef REALHEAP
  free(s);
#else
  mallcount -= 1L;
  if (mallcount == 0L) {
    /*fprintf(stderr, "mmalloc: all free - resetting top heap\n");*/
    bot = &heap[lowheap = MAX_HEAP];
  } else {
    *s = '\0';
    /*fprintf(stderr, "topheapfree: &heap[%ld]\n", (long)(s-heap));*/
  }
#endif
}

