/******************************************************************

    $Header$

    Module: atom.C

    Author: Jeff Lait

    Copyright 1997 Ytinasin.

    Description: Implements atom handling routines

 ******************************************************************/

/******************************************************************

  Revision Record

    Rev Date        Auth    Changes
    === ====        ====    =======

    0.0 12/8/97 jml     start

 ******************************************************************/

/******************************************************************
 * INCLUDES:
 ******************************************************************/
#include "defines.h"
#include "atom.h"
#include "symtable.h"
#include "evaluate.h"
#include <ctype.h>

/******************************************************************
 * DEFINES:
 ******************************************************************/
#define ATOM_HEAP_SIZE 1023

/******************************************************************
 * STRUCTURES:
 ******************************************************************/
struct ATOM_HEAP
{
    ATOM_HEAP *next;
    ATOM atoms[ATOM_HEAP_SIZE];
};


/******************************************************************
 * GLOBAL VARIABLES:
 ******************************************************************/
ATOM_MNGR glbAtomMgr;


/******************************************************************
 * ATOM_MNGR function defn
 ******************************************************************/
/******************************************************************
 * ATOM_MNGR::ATOM_MNGR
 * Initial atoms is the base heap.  NOT to be freed!  Created on stack.
 ******************************************************************/
ATOM_MNGR::ATOM_MNGR(ATOM_SMALL_HEAP *startheap)
{
    ATOM_HEAP   *heap;
    int          i;

    m_freelist = NULL;
    m_heap = NULL;
    m_hasstartheap = FALSE;

    if (startheap)
    {
//uCerr << uAcquire << this << " has acquired small heap" << endl << uRelease;
	m_hasstartheap = TRUE;
	heap = (ATOM_HEAP *) startheap;
	m_freelist = &heap->atoms[0];
	for (i = 0; i < SMALL_ATOM_HEAP_SIZE-1; i++)
	{
	    heap->atoms[i].next = &heap->atoms[i+1];
#ifdef ATOM_DEBUG
	    heap->atoms[i].owner = this;
#endif
	}
	heap->atoms[SMALL_ATOM_HEAP_SIZE-1].next = NULL;
#ifdef ATOM_DEBUG
	heap->atoms[SMALL_ATOM_HEAP_SIZE-1].owner = this;
#endif

	heap->next = m_heap;
	m_heap = heap;
    }
}


/******************************************************************
 * ATOM_MNGR::~ATOM_MNGR
 ******************************************************************/
ATOM_MNGR::~ATOM_MNGR()
{
    ATOM_HEAP       *next;

//uCerr << uAcquire << this << " has begun to die..." << endl << uRelease;
    while (m_heap && (!m_hasstartheap || m_heap->next))
    {
        next = m_heap->next;
        delete m_heap;
        m_heap = next;
    }
//uCerr << uAcquire << this << " has died... heap: " << m_heap << endl << uRelease;
}


/******************************************************************
 * ATOM_MNGR::Destroy
 * NB: Recursive!
 ******************************************************************/
void ATOM_MNGR::Destroy(ATOM *atom)
{
    ATOM *cur, *next;

    if (atom)
    {
#ifdef ATOM_DEBUG
	assert(atom->owner == this);
#endif
        cur = atom;
        while (cur)
        {
            next = cur->next;
            // Delete special data:
            switch (cur->type)
            {
                case LIST:
                case FLIST:
                    Destroy((ATOM *)cur->data.p);
                    break;

                case SYMBOL:
                case STRING:
                    if (cur->data.p)
                        FreeMem((char *) cur->data.p);
                    break;

                case TRUE:
                case FALSE:
                case NUMBER:
                    break;

                case AFUTURE:
                    ((FUTURE *)cur->data.p)->removeRef();
                    break;

                default:
                    assert(FALSE);
                    break;
            }
        // Add to free list:
        cur->next = m_freelist;
        m_freelist = cur;

        cur = next;
        }     //  While cur...
    }     //  if atom...
}

/******************************************************************
 * ATOM_MNGR::Alloc
 ******************************************************************/
ATOM *ATOM_MNGR::Alloc()
{
    ATOM *result;

    if (!m_freelist)
    {
        ATOM_HEAP   *heap;
        int          i;

        heap = new ATOM_HEAP;
        m_freelist = &heap->atoms[0];
        for (i = 0; i < ATOM_HEAP_SIZE-1; i++)
        {
            heap->atoms[i].next = &heap->atoms[i+1];
#ifdef ATOM_DEBUG
	    heap->atoms[i].owner = this;
#endif
        }
        heap->atoms[ATOM_HEAP_SIZE-1].next = NULL;
#ifdef ATOM_DEBUG
	heap->atoms[ATOM_HEAP_SIZE-1].owner = this;
#endif

        heap->next = m_heap;
        m_heap = heap;
    }

    result = m_freelist;
    m_freelist = m_freelist->next;

    result->type = INVALID;
    result->quote = 0;
    result->data.f = 0.0;
    result->data.p = NULL;
    result->next = NULL;
    return result;
}


/******************************************************************
 * ATOM_MNGR::CreateFalse
 ******************************************************************/
ATOM *ATOM_MNGR::CreateFalse()
{
    ATOM *result;

    result = Alloc();
    result->type = FALSE;
    return result;
}

/******************************************************************
 * ATOM_MNGR::CreateTrue
 ******************************************************************/
ATOM *ATOM_MNGR::CreateTrue()
{
    ATOM *result;

    result = Alloc();
    result->type = TRUE;
    return result;
}

/******************************************************************
 * ATOM_MNGR::Duplicate
 * NB: Recursive!
 ******************************************************************/
ATOM *ATOM_MNGR::Duplicate(ATOM *atom)
{
    ATOM    *result;

    if (atom)
    {
        result = Alloc();
        result->type = atom->type;
        result->quote = atom->quote;

        switch (atom->type)
        {
            case LIST:
            case FLIST:
            {
                ATOM *cur, *head, *src;

                // NB: next defaults to NULL!
                head = NULL;
                for (src = (ATOM *) atom->data.p; src; src = src->next)
                {
#ifdef ATOM_DEBUG
		    // Heterogenus lists are EVIL
		    assert(atom->owner == src->owner);
#endif
                    if (!head)
                    {
                        cur = Duplicate(src);
                        head = cur;
                    }
                    else
                    {
                        cur->next = Duplicate(src);
                        cur = cur->next;
                    }
                }
                result->data.p = (void *) head;
                break;
            }

            case STRING:
            case SYMBOL:
                result->data.p = (void *) ourstrdup((char *)atom->data.p);
                break;

            case NUMBER:
                result->data.f = atom->data.f;
                break;

            case TRUE:
            case FALSE:
                break;

            case AFUTURE:
                result->data.p = atom->data.p;
                ((FUTURE *) result->data.p)->addRef();
                break;

            default:
                assert(FALSE);
                break;
        }
    }
    else
    {
        // Duplicate of NULL is empty list.  Silly convention IMO
        result = Alloc();
        result->type = LIST;
    }

    return result;
}


/******************************************************************
 * FUTURE member funcs
 ******************************************************************/
/******************************************************************
 * FUTURE::FUTURE
 ******************************************************************/
FUTURE::FUTURE()
{
    m_result = NULL;
    m_refcnt = 0;
}

/******************************************************************
 * FUTURE::~FUTURE
 ******************************************************************/
FUTURE::~FUTURE()
{
    glbAtomMgr.Destroy(m_result);
}

/******************************************************************
 * FUTURE::addRef
 ******************************************************************/
void FUTURE::addRef()
{
    m_refcnt++;
}

/******************************************************************
 * FUTURE::removeRef
 ******************************************************************/
void FUTURE::removeRef()
{
    m_refcnt--;
    if (m_refcnt < 1)
        delete this;
}

/******************************************************************
 * FUTURE::getData
 ******************************************************************/
ATOM *FUTURE::getData()
{
    if (!m_result)
    {
        // Not ready yet...
        uWait m_wait;
        // Awoken, chain alert:
        uSignal m_wait;
    }
    return m_result;
}

/******************************************************************
 * FUTURE::writeData
 * NB: Duplicates data into global atom list!
 ******************************************************************/
void FUTURE::writeData(ATOM *data)
{
    assert(data);           // Used to determine if there or not!!
    m_result = glbAtomMgr.Duplicate(data);
    uSignal m_wait;
}

/******************************************************************
 * FUTURE::isReady
 ******************************************************************/
bool FUTURE::isReady() const
{
    return (m_result ? TRUE : FALSE);
}

/******************************************************************
 * PrintAtom
 ******************************************************************/
void PrintAtom(ATOM *atom, uOStream &output)
{
    while (atom) 
    {
        if (atom->quote) 
        {
            output << "'";
        }
        switch (atom->type) 
        {
            case LIST:
                output << "( ";
                PrintAtom((ATOM *)atom->data.p, output);
                output << ") ";
                break;
            
            case SYMBOL:
                output << (char *) atom->data.p << " ";
                break;

            case NUMBER:
                output << atom->data.f << " ";
                break;

            case STRING:
                output << "\"" << (char *) atom->data.p << "\" ";
                break;

            case FALSE:
                output << "#f ";
                break;

            case TRUE:
                output << "#t ";
                break;

            case AFUTURE:
                output << "< FUTURE: ";
		if (((FUTURE *)atom->data.p)->isReady())
		{
		    PrintAtom(((FUTURE *)atom->data.p)->getData(), output);
		    output << "> ";
		}
		else
		{
		    output << "waiting > ";
		}
                break;

            case FLIST:
                output << "{ ";
                PrintAtom((ATOM *)atom->data.p, output);
                output << "} ";
                break;

            default:
                uCerr << "Invalid type " << atom->type << " printed" << endl;
                assert(FALSE);
                break;
        }
        atom = atom->next;
    }   // End while
}


/******************************************************************
 * PrintExpandAtom
 ******************************************************************/
void PrintExpandAtom(ATOM *atom, SYMTABLE *local, uOStream &output)
{
    if (atom && (atom->type == SYMBOL)) 
    {
        SYMENTRY *sym;
        PARAM_LIST *param;

        output << "Symbol " << (char *) atom->data.p << " resolves to";
        sym = local->LookupEntry((char *)atom->data.p);
        if (!sym) 
        {
            output << " unknown symbol\n";
            return;
        }
        else if (sym->type == INTERNAL) 
        {
            output << " internal symbol\n";
        }
        else if (sym->type == FUNC) 
        {
            output << " function taking ";
            for (param = sym->param; param; param = param->next) {
                output << param->name << ", ";
                }
            output << "\b\b  \nBody is:\n";
            PrintExpandAtom(sym->body, local, output);
        }
        else 
        {
            output << " alias of:\n";
            PrintExpandAtom(sym->body, local, output);
        }
    }
    else 
    {
        ATOM *temp;

        // Don't print siblings!
        temp = GLB_GetAtomHeap.Duplicate(atom);
        PrintAtom(temp, output);
        output << "\n";
        GLB_GetAtomHeap.Destroy(temp);
    }
}


/******************************************************************
 * ParseLine
 * Gotta love the indentation style....
 * NB: Atoms are created on global heap!
 ******************************************************************/
ATOM *ParseLine(char *in, int *pos, int onexpr)
{
    ATOM *head, *cur;
    char *temp;
    int indx, i1;
    int quote = FALSE;

    indx = *pos;
    head = cur = NULL;
    while (in[indx]) {
        switch (in[indx]) {
            case ';':
                // comment, ignore rest of line or input...
                while (in[indx] && (in[indx] != 0xa) && (in[indx] != 0xd)) indx++;
                break;

            case '\'':
                // Quote operator: set next atom to unevaluated...
                indx++;
                quote = TRUE;
                break;

            case ')':
            case '}':
                // Hit end of our list, quick end...
                // NB: Does not match () and {}
                *pos = indx + 1;
                return(head);

            case '#':
            {
                ATOM *newatom;

                newatom = (in[indx+1] == 't') ? glbAtomMgr.CreateTrue() : glbAtomMgr.CreateFalse();
                if (!head) {
                    cur = newatom;
                    head = cur;
                    }
                else {
                    cur->next = newatom;
                    cur = cur->next;
                    }
                cur->quote = quote;
                quote = FALSE;
                indx += 2;
                break;
            }
                
            case '(':
                // New list...
                if (!head) {
                    cur = glbAtomMgr.Alloc();
                    head = cur;
                    }
                else {
                    cur->next = glbAtomMgr.Alloc();
                    cur = cur->next;
                    }
                cur->type = LIST;
                indx++;
                cur->data.p = (void *) ParseLine(in, &indx);
                cur->quote = quote;
                quote = FALSE;

                if (onexpr)         // Only parse one list!
                {
                    *pos = indx;
                    return head;
                }
                break;

            case '{':
                // New list...
                if (!head) {
                    cur = glbAtomMgr.Alloc();
                    head = cur;
                    }
                else {
                    cur->next = glbAtomMgr.Alloc();
                    cur = cur->next;
                    }
                cur->type = FLIST;
                indx++;
                cur->data.p = (void *) ParseLine(in, &indx);
                cur->quote = quote;
                quote = FALSE;

                if (onexpr)         // Only parse one list!
                {
                    *pos = indx;
                    return head;
                }
                break;

            case '\"':
                // String...
                for (i1 = indx+1; in[i1] && (in[i1] != '\"'); i1++);
                if (!in[i1]) {
                    printf("Unterminated quote!!\n");
                    break;
                    }
                temp = GetMem(i1 - indx + 1);
                memcpy(temp, &in[indx+1], i1 - indx - 1);
                temp[i1 - indx - 1] = 0;
                indx = i1+1;
                if (!head) {
                    cur = glbAtomMgr.Alloc();
                    head = cur;
                    }
                else {
                    cur->next = glbAtomMgr.Alloc();
                    cur = cur->next;
                    }
                cur->type = STRING;
                cur->data.p = (void *) temp;
                cur->quote = quote;
                quote = FALSE;
                if (onexpr)         // Only parse one list!
                {
                    *pos = indx;
                    return head;
                }
                break;

            case '~':
                // Special whitespace...
                indx++;
                break;
    
            default:
                if (isdigit(in[indx]) || in[indx] == '.' || (in[indx] == '-' && (in[indx+1] == '.' || isdigit(in[indx+1])))) {
                    if (!head) {
                        cur = glbAtomMgr.Alloc();
                        head = cur;
                        }
                    else {
                        cur->next = glbAtomMgr.Alloc();
                        cur = cur->next;
                        }
                    cur->type = NUMBER;
                    cur->quote = quote;
                    quote = FALSE;
                    if (in[indx] == '-') {
                        cur->data.f = -atof(&in[indx+1]);
                        indx++;
                        }
                    else {
                        cur->data.f = atof(&in[indx]);
                        }
                    while (isdigit(in[indx]) || in[indx] == '.') indx++;
                    if (onexpr)         // Only parse one list!
                    {
                        *pos = indx;
                        return head;
                    }
                    }
                else if (isspace(in[indx])) {
                    indx++;
                    }
                else { 
                    // Symbol...
                    if (!head) {
                        cur = glbAtomMgr.Alloc();
                        head = cur;
                        }
                    else {
                        cur->next = glbAtomMgr.Alloc();
                        cur = cur->next;
                        }
                    cur->quote = quote;
                    quote = FALSE;
                    cur->type = SYMBOL;
                    for (i1 = indx; in[i1] && !isspace(in[i1]) && 
                         (in[i1] != '(') && (in[i1] != ')'); i1++);
                    temp = GetMem(i1 - indx +2);
                    memcpy(temp, &in[indx], i1 - indx);
                    temp[i1 - indx] = 0;
                    indx = i1;
                    cur->data.p = (void *) temp;
                    if (onexpr)         // Only parse one list!
                    {
                        *pos = indx;
                        return head;
                    }
                    }
                break;
            }   // End switch in[indx]
        }       // End while in[indx]
    *pos = indx;
    return(head);
    }

