/*
 * ------------------------------------------------------------------------
 *      PACKAGE:  [incr Tcl]
 *  DESCRIPTION:  Object-Oriented Extensions to Tcl
 *
 *  [incr Tcl] provides object-oriented extensions to Tcl, much as
 *  C++ provides object-oriented extensions to C.  It provides a means
 *  of encapsulating related procedures together with their shared data
 *  in a local namespace that is hidden from the outside world.  It
 *  promotes code re-use through inheritance.  More than anything else,
 *  it encourages better organization of Tcl applications through the
 *  object-oriented paradigm, leading to code that is easier to
 *  understand and maintain.
 *
 *  This segment provides common utility functions used throughout
 *  the other [incr Tcl] source files.
 *
 * ========================================================================
 *  AUTHOR:  Michael J. McLennan
 *           Bell Labs Innovations for Lucent Technologies
 *           mmclennan@lucent.com
 *           http://www.tcltk.com/itcl
 * ========================================================================
 *           Copyright (c) 1993-1998  Lucent Technologies, Inc.
 * ------------------------------------------------------------------------
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */
#include "itclInt.h"

/*
 *  POOL OF LIST ELEMENTS FOR LINKED LIST
 */
static Itcl_ListElem *listPool = NULL;
static int listPoolLen = 0;

#define ITCL_VALID_LIST 0x01face10  /* magic bit pattern for validation */
#define ITCL_LIST_POOL_SIZE 200     /* max number of elements in listPool */


/*
 *  These records are used to keep track of reference-counted data
 *  for Itcl_PreserveData and Itcl_ReleaseData.
 */
typedef struct ItclPreservedData {
    ClientData data;                /* reference to data */
    int usage;                      /* number of active uses */
    Tcl_FreeProc *fproc;            /* procedure used to free data */
} ItclPreservedData;

static Tcl_HashTable *ItclPreservedList = NULL;
TCL_DECLARE_MUTEX(ItclPreservedListLock)

/*
 *  This structure is used to take a snapshot of the interpreter
 *  state in Itcl_SaveInterpState.  You can snapshot the state,
 *  execute a command, and then back up to the result or the
 *  error that was previously in progress.
 */
typedef struct InterpState {
    int validate;                   /* validation stamp */
    int status;                     /* return code status */
    Tcl_Obj *objResult;             /* result object */
    char *errorInfo;                /* contents of errorInfo variable */
    char *errorCode;                /* contents of errorCode variable */
} InterpState;

#define TCL_STATE_VALID 0x01233210  /* magic bit pattern for validation */


/*
 * ------------------------------------------------------------------------
 *  Itcl_Assert()
 *
 *  Called whenever an assert() test fails.  Prints a diagnostic
 *  message and abruptly exits.
 * ------------------------------------------------------------------------
 */

void
Itcl_Assert(testExpr, fileName, lineNumber)
    CONST char *testExpr;   /* string representing test expression */
    CONST char *fileName;   /* file name containing this call */
    int lineNumber;	    /* line number containing this call */
{
    Tcl_Panic("Itcl Assertion failed: \"%s\" (line %d of %s)",
	testExpr, lineNumber, fileName);
}



/*
 * ------------------------------------------------------------------------
 *  Itcl_InitStack()
 *
 *  Initializes a stack structure, allocating a certain amount of memory
 *  for the stack and setting the stack length to zero.
 * ------------------------------------------------------------------------
 */
void
Itcl_InitStack(stack)
    Itcl_Stack *stack;     /* stack to be initialized */
{
    stack->values = stack->space;
    stack->max = sizeof(stack->space)/sizeof(ClientData);
    stack->len = 0;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_DeleteStack()
 *
 *  Destroys a stack structure, freeing any memory that may have been
 *  allocated to represent it.
 * ------------------------------------------------------------------------
 */
void
Itcl_DeleteStack(stack)
    Itcl_Stack *stack;     /* stack to be deleted */
{
    /*
     *  If memory was explicitly allocated (instead of using the
     *  built-in buffer) then free it.
     */
    if (stack->values != stack->space) {
        ckfree((char*)stack->values);
    }
    stack->values = NULL;
    stack->len = stack->max = 0;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_PushStack()
 *
 *  Pushes a piece of client data onto the top of the given stack.
 *  If the stack is not large enough, it is automatically resized.
 * ------------------------------------------------------------------------
 */
void
Itcl_PushStack(cdata,stack)
    ClientData cdata;      /* data to be pushed onto stack */
    Itcl_Stack *stack;     /* stack */
{
    ClientData *newStack;

    if (stack->len+1 >= stack->max) {
        stack->max = 2*stack->max;
        newStack = (ClientData*)
            ckalloc((unsigned)(stack->max*sizeof(ClientData)));

        if (stack->values) {
            memcpy((char*)newStack, (char*)stack->values,
                (size_t)(stack->len*sizeof(ClientData)));

            if (stack->values != stack->space)
                ckfree((char*)stack->values);
        }
        stack->values = newStack;
    }
    stack->values[stack->len++] = cdata;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_PopStack()
 *
 *  Pops a bit of client data from the top of the given stack.
 * ------------------------------------------------------------------------
 */
ClientData
Itcl_PopStack(stack)
    Itcl_Stack *stack;  /* stack to be manipulated */
{
    if (stack->values && (stack->len > 0)) {
        stack->len--;
        return stack->values[stack->len];
    }
    return (ClientData)NULL;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_PeekStack()
 *
 *  Gets the current value from the top of the given stack.
 * ------------------------------------------------------------------------
 */
ClientData
Itcl_PeekStack(stack)
    Itcl_Stack *stack;  /* stack to be examined */
{
    if (stack->values && (stack->len > 0)) {
        return stack->values[stack->len-1];
    }
    return (ClientData)NULL;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_GetStackValue()
 *
 *  Gets a value at some index within the stack.  Index "0" is the
 *  first value pushed onto the stack.
 * ------------------------------------------------------------------------
 */
ClientData
Itcl_GetStackValue(stack,pos)
    Itcl_Stack *stack;  /* stack to be examined */
    int pos;            /* get value at this index */
{
    if (stack->values && (stack->len > 0)) {
        assert(pos < stack->len);
        return stack->values[pos];
    }
    return (ClientData)NULL;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_InitList()
 *
 *  Initializes a linked list structure, setting the list to the empty
 *  state.
 * ------------------------------------------------------------------------
 */
void
Itcl_InitList(listPtr)
    Itcl_List *listPtr;     /* list to be initialized */
{
    listPtr->validate = ITCL_VALID_LIST;
    listPtr->num      = 0;
    listPtr->head     = NULL;
    listPtr->tail     = NULL;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_DeleteList()
 *
 *  Destroys a linked list structure, deleting all of its elements and
 *  setting it to an empty state.  If the elements have memory associated
 *  with them, this memory must be freed before deleting the list or it
 *  will be lost.
 * ------------------------------------------------------------------------
 */
void
Itcl_DeleteList(listPtr)
    Itcl_List *listPtr;     /* list to be deleted */
{
    Itcl_ListElem *elemPtr;

    assert(listPtr->validate == ITCL_VALID_LIST);

    elemPtr = listPtr->head;
    while (elemPtr) {
        elemPtr = Itcl_DeleteListElem(elemPtr);
    }
    listPtr->validate = 0;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_CreateListElem()
 *
 *  Low-level routined used by procedures like Itcl_InsertList() and
 *  Itcl_AppendList() to create new list elements.  If elements are
 *  available, one is taken from the list element pool.  Otherwise,
 *  a new one is allocated.
 * ------------------------------------------------------------------------
 */
Itcl_ListElem*
Itcl_CreateListElem(listPtr)
    Itcl_List *listPtr;     /* list that will contain this new element */
{
    Itcl_ListElem *elemPtr;

    if (listPoolLen > 0) {
        elemPtr = listPool;
        listPool = elemPtr->next;
        --listPoolLen;
    }
    else {
        elemPtr = (Itcl_ListElem*)ckalloc((unsigned)sizeof(Itcl_ListElem));
    }
    elemPtr->owner = listPtr;
    elemPtr->value = NULL;
    elemPtr->next  = NULL;
    elemPtr->prev  = NULL;

    return elemPtr;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_DeleteListElem()
 *
 *  Destroys a single element in a linked list, returning it to a pool of
 *  elements that can be later reused.  Returns a pointer to the next
 *  element in the list.
 * ------------------------------------------------------------------------
 */
Itcl_ListElem*
Itcl_DeleteListElem(elemPtr)
    Itcl_ListElem *elemPtr;     /* list element to be deleted */
{
    Itcl_List *listPtr;
    Itcl_ListElem *nextPtr;

    nextPtr = elemPtr->next;

    if (elemPtr->prev) {
        elemPtr->prev->next = elemPtr->next;
    }
    if (elemPtr->next) {
        elemPtr->next->prev = elemPtr->prev;
    }

    listPtr = elemPtr->owner;
    if (elemPtr == listPtr->head)
        listPtr->head = elemPtr->next;
    if (elemPtr == listPtr->tail)
        listPtr->tail = elemPtr->prev;
    --listPtr->num;

    if (listPoolLen < ITCL_LIST_POOL_SIZE) {
        elemPtr->next = listPool;
        listPool = elemPtr;
        ++listPoolLen;
    }
    else {
        ckfree((char*)elemPtr);
    }
    return nextPtr;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_InsertList()
 *
 *  Creates a new list element containing the given value and returns
 *  a pointer to it.  The element is inserted at the beginning of the
 *  specified list.
 * ------------------------------------------------------------------------
 */
Itcl_ListElem*
Itcl_InsertList(listPtr,val)
    Itcl_List *listPtr;     /* list being modified */
    ClientData val;         /* value associated with new element */
{
    Itcl_ListElem *elemPtr;
    assert(listPtr->validate == ITCL_VALID_LIST);

    elemPtr = Itcl_CreateListElem(listPtr);

    elemPtr->value = val;
    elemPtr->next  = listPtr->head;
    elemPtr->prev  = NULL;
    if (listPtr->head) {
        listPtr->head->prev = elemPtr;
    }
    listPtr->head  = elemPtr;
    if (listPtr->tail == NULL) {
        listPtr->tail = elemPtr;
    }
    ++listPtr->num;

    return elemPtr;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_InsertListElem()
 *
 *  Creates a new list element containing the given value and returns
 *  a pointer to it.  The element is inserted in the list just before
 *  the specified element.
 * ------------------------------------------------------------------------
 */
Itcl_ListElem*
Itcl_InsertListElem(pos,val)
    Itcl_ListElem *pos;     /* insert just before this element */
    ClientData val;         /* value associated with new element */
{
    Itcl_List *listPtr;
    Itcl_ListElem *elemPtr;

    listPtr = pos->owner;
    assert(listPtr->validate == ITCL_VALID_LIST);
    assert(pos != NULL);

    elemPtr = Itcl_CreateListElem(listPtr);
    elemPtr->value = val;

    elemPtr->prev = pos->prev;
    if (elemPtr->prev) {
        elemPtr->prev->next = elemPtr;
    }
    elemPtr->next = pos;
    pos->prev     = elemPtr;

    if (listPtr->head == pos) {
        listPtr->head = elemPtr;
    }
    if (listPtr->tail == NULL) {
        listPtr->tail = elemPtr;
    }
    ++listPtr->num;

    return elemPtr;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_AppendList()
 *
 *  Creates a new list element containing the given value and returns
 *  a pointer to it.  The element is appended at the end of the
 *  specified list.
 * ------------------------------------------------------------------------
 */
Itcl_ListElem*
Itcl_AppendList(listPtr,val)
    Itcl_List *listPtr;     /* list being modified */
    ClientData val;         /* value associated with new element */
{
    Itcl_ListElem *elemPtr;
    assert(listPtr->validate == ITCL_VALID_LIST);

    elemPtr = Itcl_CreateListElem(listPtr);

    elemPtr->value = val;
    elemPtr->prev  = listPtr->tail;
    elemPtr->next  = NULL;
    if (listPtr->tail) {
        listPtr->tail->next = elemPtr;
    }
    listPtr->tail  = elemPtr;
    if (listPtr->head == NULL) {
        listPtr->head = elemPtr;
    }
    ++listPtr->num;

    return elemPtr;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_AppendListElem()
 *
 *  Creates a new list element containing the given value and returns
 *  a pointer to it.  The element is inserted in the list just after
 *  the specified element.
 * ------------------------------------------------------------------------
 */
Itcl_ListElem*
Itcl_AppendListElem(pos,val)
    Itcl_ListElem *pos;     /* insert just after this element */
    ClientData val;         /* value associated with new element */
{
    Itcl_List *listPtr;
    Itcl_ListElem *elemPtr;

    listPtr = pos->owner;
    assert(listPtr->validate == ITCL_VALID_LIST);
    assert(pos != NULL);

    elemPtr = Itcl_CreateListElem(listPtr);
    elemPtr->value = val;

    elemPtr->next = pos->next;
    if (elemPtr->next) {
        elemPtr->next->prev = elemPtr;
    }
    elemPtr->prev = pos;
    pos->next     = elemPtr;

    if (listPtr->tail == pos) {
        listPtr->tail = elemPtr;
    }
    if (listPtr->head == NULL) {
        listPtr->head = elemPtr;
    }
    ++listPtr->num;

    return elemPtr;
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_SetListValue()
 *
 *  Modifies the value associated with a list element.
 * ------------------------------------------------------------------------
 */
void
Itcl_SetListValue(elemPtr,val)
    Itcl_ListElem *elemPtr; /* list element being modified */
    ClientData val;         /* new value associated with element */
{
    assert(elemPtr != NULL);
    assert(elemPtr->owner != NULL);
    assert(elemPtr->owner->validate == ITCL_VALID_LIST);

    elemPtr->value = val;
}


/*
 * ========================================================================
 *  REFERENCE-COUNTED DATA
 *
 *  The following procedures manage generic reference-counted data.
 *  They are similar in spirit to the Tcl_Preserve/Tcl_Release
 *  procedures defined in the Tcl/Tk core.  But these procedures use
 *  a hash table instead of a linked list to maintain the references,
 *  so they scale better.  Also, the Tcl procedures have a bad behavior
 *  during the "exit" command.  Their exit handler shuts them down
 *  when other data is still being reference-counted and cleaned up.
 *
 * ------------------------------------------------------------------------
 *  Itcl_EventuallyFree()
 *
 *  Registers a piece of data so that it will be freed when no longer
 *  in use.  The data is registered with an initial usage count of "0".
 *  Future calls to Itcl_PreserveData() increase this usage count, and
 *  calls to Itcl_ReleaseData() decrease the count until it reaches
 *  zero and the data is freed.
 * ------------------------------------------------------------------------
 */
void
Itcl_EventuallyFree(cdata, fproc)
    ClientData cdata;          /* data to be freed when not in use */
    Tcl_FreeProc *fproc;       /* procedure called to free data */
{
    int newEntry;
    Tcl_HashEntry *entry;
    ItclPreservedData *chunk;

    /*
     *  If the clientData value is NULL, do nothing.
     */
    if (cdata == NULL) {
        return;
    }

    /*
     *  If a list has not yet been created to manage bits of
     *  preserved data, then create it.
     */
    Tcl_MutexLock(&ItclPreservedListLock);
    if (!ItclPreservedList) {
        ItclPreservedList = (Tcl_HashTable*)ckalloc(
            (unsigned)sizeof(Tcl_HashTable)
        );
        Tcl_InitHashTable(ItclPreservedList, TCL_ONE_WORD_KEYS);
    }

    /*
     *  Find or create the data in the global list.
     */
    entry = Tcl_CreateHashEntry(ItclPreservedList,(char*)cdata, &newEntry);
    if (newEntry) {
        chunk = (ItclPreservedData*)ckalloc(
            (unsigned)sizeof(ItclPreservedData)
        );
        chunk->data  = cdata;
        chunk->usage = 0;
        chunk->fproc = fproc;
        Tcl_SetHashValue(entry, (ClientData)chunk);
    }
    else {
        chunk = (ItclPreservedData*)Tcl_GetHashValue(entry);
        chunk->fproc = fproc;
    }

    /*
     *  If the usage count is zero, then delete the data now.
     */
    if (chunk->usage == 0) {
	chunk->usage = -1;  /* cannot preserve/release anymore */

	Tcl_MutexUnlock(&ItclPreservedListLock);
	(*chunk->fproc)((char*)chunk->data);
	Tcl_MutexLock(&ItclPreservedListLock);
	Tcl_DeleteHashEntry(entry);
	ckfree((char*)chunk);
    }
    Tcl_MutexUnlock(&ItclPreservedListLock);
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_PreserveData()
 *
 *  Increases the usage count for a piece of data that will be freed
 *  later when no longer needed.  Each call to Itcl_PreserveData()
 *  puts one claim on a piece of data, and subsequent calls to
 *  Itcl_ReleaseData() remove those claims.  When Itcl_EventuallyFree()
 *  is called, and when the usage count reaches zero, the data is
 *  freed.
 * ------------------------------------------------------------------------
 */
void
Itcl_PreserveData(cdata)
    ClientData cdata;      /* data to be preserved */
{
    Tcl_HashEntry *entry;
    ItclPreservedData *chunk;
    int newEntry;

    /*
     *  If the clientData value is NULL, do nothing.
     */
    if (cdata == NULL) {
        return;
    }

    /*
     *  If a list has not yet been created to manage bits of
     *  preserved data, then create it.
     */
    Tcl_MutexLock(&ItclPreservedListLock);
    if (!ItclPreservedList) {
        ItclPreservedList = (Tcl_HashTable*)ckalloc(
            (unsigned)sizeof(Tcl_HashTable)
        );
        Tcl_InitHashTable(ItclPreservedList,TCL_ONE_WORD_KEYS);
    }

    /*
     *  Find the data in the global list and bump its usage count.
     */
    entry = Tcl_CreateHashEntry(ItclPreservedList,(char*)cdata, &newEntry);
    if (newEntry) {
        chunk = (ItclPreservedData*)ckalloc(
            (unsigned)sizeof(ItclPreservedData)
        );
        chunk->data  = cdata;
        chunk->usage = 0;
        chunk->fproc = NULL;
        Tcl_SetHashValue(entry, (ClientData)chunk);
    }
    else {
        chunk = (ItclPreservedData*)Tcl_GetHashValue(entry);
    }

    /*
     *  Only increment the usage if it is non-negative.
     *  Negative numbers mean that the data is in the process
     *  of being destroyed by Itcl_ReleaseData(), and should
     *  not be further preserved.
     */
    if (chunk->usage >= 0) {
        chunk->usage++;
    }
    Tcl_MutexUnlock(&ItclPreservedListLock);
}

/*
 * ------------------------------------------------------------------------
 *  Itcl_ReleaseData()
 *
 *  Decreases the usage count for a piece of data that was registered
 *  previously via Itcl_PreserveData().  After Itcl_EventuallyFree()
 *  is called and the usage count reaches zero, the data is
 *  automatically freed.
 * ------------------------------------------------------------------------
 */
void
Itcl_ReleaseData(cdata)
    ClientData cdata;      /* data to be released */
{
    Tcl_HashEntry *entry;
    ItclPreservedData *chunk;

    /*
     *  If the clientData value is NULL, do nothing.
     */
    if (cdata == NULL) {
        return;
    }

    /*
     *  Otherwise, find the data in the global list and
     *  decrement its usage count.
     */
    entry = NULL;
    Tcl_MutexLock(&ItclPreservedListLock);
    if (ItclPreservedList) {
        entry = Tcl_FindHashEntry(ItclPreservedList,(char*)cdata);
    }
    if (!entry) {
	Tcl_MutexUnlock(&ItclPreservedListLock);
        Tcl_Panic("Itcl_ReleaseData can't find reference for 0x%p", cdata);
    }

    /*
     *  Only decrement the usage if it is non-negative.
     *  When the usage reaches zero, set it to a negative number
     *  to indicate that data is being destroyed, and then
     *  invoke the client delete proc.  When the data is deleted,
     *  remove the entry from the preservation list.
     */
    chunk = (ItclPreservedData*)Tcl_GetHashValue(entry);
    if (chunk->usage > 0 && --chunk->usage == 0) {

	if (chunk->fproc) {
	    chunk->usage = -1;  /* cannot preserve/release anymore */
	    Tcl_MutexUnlock(&ItclPreservedListLock);
	    (*chunk->fproc)((char*)chunk->data);
	    Tcl_MutexLock(&ItclPreservedListLock);
        }

        Tcl_DeleteHashEntry(entry);
        ckfree((char*)chunk);
    }
    Tcl_MutexUnlock(&ItclPreservedListLock);
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_SaveInterpState()
 *
 *  Takes a snapshot of the current result state of the interpreter.
 *  The snapshot can be restored at any point by Itcl_RestoreInterpState.
 *  So if you are in the middle of building a return result, you can
 *  snapshot the interpreter, execute a command that might generate an
 *  error, restore the snapshot, and continue building the result string.
 *
 *  Once a snapshot is saved, it must be restored by calling
 *  Itcl_RestoreInterpState, or discarded by calling
 *  Itcl_DiscardInterpState.  Otherwise, memory will be leaked.
 *
 *  Returns a token representing the state of the interpreter.
 * ------------------------------------------------------------------------
 */
Itcl_InterpState
Itcl_SaveInterpState(interp, status)
    Tcl_Interp* interp;     /* interpreter being modified */
    int status;             /* integer status code for current operation */
{
    Interp *iPtr = (Interp*)interp;

    InterpState *info;
    CONST char *val;

    /*
     * ERR_IN_PROGRESS was replaced by new APIs in 8.5a2.  Call them if they
     * are available, or somehow magic them in from the stubs table.
     * Tcl_ChannelThreadActionProc is a stubs slot higher than the APIs we
     * need, so its existence indicates slot-y goodness.
     */
#ifndef ERR_IN_PROGRESS
    return (Itcl_InterpState) Tcl_SaveInterpState(interp, status);
#elif defined(USE_TCL_STUBS) && defined(Tcl_ChannelThreadActionProc)
    if (itclCompatFlags & ITCL_COMPAT_USE_ISTATE_API) {
	Itcl_InterpState (*tcl_SaveInterpState)(Tcl_Interp *, int) =
	    (Itcl_InterpState (*)(Tcl_Interp *, int)) tclStubsPtr->reserved535;
	return (*tcl_SaveInterpState)(interp, status);
    }
#endif

    info = (InterpState*)ckalloc(sizeof(InterpState));
    info->validate = TCL_STATE_VALID;
    info->status = status;
    info->errorInfo = NULL;
    info->errorCode = NULL;

    /*
     *  Get the result object from the interpreter.  This synchronizes
     *  the old-style result, so we don't have to worry about it.
     *  Keeping the object result is enough.
     */
    info->objResult = Tcl_GetObjResult(interp);
    Tcl_IncrRefCount(info->objResult);

    /*
     *  If an error is in progress, preserve its state.
     */
#ifdef ERR_IN_PROGRESS   /* this disappeared in 8.5a2 */
    if ((iPtr->flags & ERR_IN_PROGRESS) != 0) {
#else
    if (iPtr->errorInfo != NULL) {
#endif
        val = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
        if (val) {
            info->errorInfo = ckalloc((unsigned)(strlen(val)+1));
            strcpy(info->errorInfo, val);
        }

        val = Tcl_GetVar(interp, "errorCode", TCL_GLOBAL_ONLY);
        if (val) {
            info->errorCode = ckalloc((unsigned)(strlen(val)+1));
            strcpy(info->errorCode, val);
        }
    }

    /*
     *  Now, reset the interpreter to a clean state.
     */
    Tcl_ResetResult(interp);

    return (Itcl_InterpState)info;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_RestoreInterpState()
 *
 *  Restores the state of the interpreter to a snapshot taken by
 *  Itcl_SaveInterpState.  This affects variables such as "errorInfo"
 *  and "errorCode".  After this call, the token for the interpreter
 *  state is no longer valid.
 *
 *  Returns the status code that was pending at the time the state was
 *  captured.
 * ------------------------------------------------------------------------
 */
int
Itcl_RestoreInterpState(interp, state)
    Tcl_Interp* interp;       /* interpreter being modified */
    Itcl_InterpState state;   /* token representing interpreter state */
{
    InterpState *info = (InterpState*)state;
    int status;

    /*
     * ERR_IN_PROGRESS was replaced by new APIs in 8.5a2.  Call them if they
     * are available, or somehow magic them in from the stubs table.
     * Tcl_ChannelThreadActionProc is a stubs slot higher than the APIs we
     * need, so its existence indicates slot-y goodness.
     */
#ifndef ERR_IN_PROGRESS
    return Tcl_RestoreInterpState(interp, (Tcl_InterpState)state);
#elif defined(USE_TCL_STUBS) && defined(Tcl_ChannelThreadActionProc)
    if (itclCompatFlags & ITCL_COMPAT_USE_ISTATE_API) {
	int (*tcl_RestoreInterpState)() = (int (*)()) tclStubsPtr->reserved536;
 	return (*tcl_RestoreInterpState)(interp, state);
    }
#endif

    if (info->validate != TCL_STATE_VALID) {
        Tcl_Panic("bad token in Itcl_RestoreInterpState");
    }
    Tcl_ResetResult(interp);

    /*
     *  If an error is in progress, restore its state.
     *  Set the error code the hard way--set the variable directly
     *  and fix the interpreter flags.  Otherwise, if the error code
     *  string is really a list, it will get wrapped in extra {}'s.
     */
    if (info->errorInfo) {
        Tcl_AddErrorInfo(interp, info->errorInfo);
        ckfree(info->errorInfo);
    }

    if (info->errorCode) {
        Tcl_SetObjErrorCode(interp, Tcl_NewStringObj(info->errorCode, -1));
        ckfree(info->errorCode);
    }

    /*
     *  Assign the object result back to the interpreter, then
     *  release our hold on it.
     */
    Tcl_SetObjResult(interp, info->objResult);
    Tcl_DecrRefCount(info->objResult);

    status = info->status;
    info->validate = 0;
    ckfree((char*)info);

    return status;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_DiscardInterpState()
 *
 *  Frees the memory associated with an interpreter snapshot taken by
 *  Itcl_SaveInterpState.  If the snapshot is not restored, this
 *  procedure must be called to discard it, or the memory will be lost.
 *  After this call, the token for the interpreter state is no longer
 *  valid.
 * ------------------------------------------------------------------------
 */
void
Itcl_DiscardInterpState(state)
    Itcl_InterpState state;  /* token representing interpreter state */
{
    InterpState *info = (InterpState*)state;

    /*
     * ERR_IN_PROGRESS was replaced by new APIs in 8.5a2.  Call them if they
     * are available, or somehow magic them in from the stubs table.
     * Tcl_ChannelThreadActionProc is a stubs slot higher than the APIs we
     * need, so its existence indicates slot-y goodness.
     */
#ifndef ERR_IN_PROGRESS
    Tcl_DiscardInterpState((Tcl_InterpState)state);
    return;
#elif defined(USE_TCL_STUBS) && defined(Tcl_ChannelThreadActionProc)
    if (itclCompatFlags & ITCL_COMPAT_USE_ISTATE_API) {
	void (* tcl_DiscardInterpState)() = (void (*)())
	    tclStubsPtr->reserved537;
	(*tcl_DiscardInterpState)(state);
	return;
    }
#endif

    if (info->validate != TCL_STATE_VALID) {
        Tcl_Panic("bad token in Itcl_DiscardInterpState");
    }

    if (info->errorInfo) {
        ckfree(info->errorInfo);
    }
    if (info->errorCode) {
        ckfree(info->errorCode);
    }
    Tcl_DecrRefCount(info->objResult);

    info->validate = 0;
    ckfree((char*)info);
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_Protection()
 *
 *  Used to query/set the protection level used when commands/variables
 *  are defined within a class.  The default protection level (when
 *  no public/protected/private command is active) is ITCL_DEFAULT_PROTECT.
 *  In the default case, new commands are treated as public, while new
 *  variables are treated as protected.
 *
 *  If the specified level is 0, then this procedure returns the
 *  current value without changing it.  Otherwise, it sets the current
 *  value to the specified protection level, and returns the previous
 *  value.
 * ------------------------------------------------------------------------
 */
int
Itcl_Protection(interp, newLevel)
    Tcl_Interp *interp;  /* interpreter being queried */
    int newLevel;        /* new protection level or 0 */
{
    int oldVal;
    ItclObjectInfo *info;

    /*
     *  If a new level was specified, then set the protection level.
     *  In any case, return the protection level as it stands right now.
     */
    info = (ItclObjectInfo*) Tcl_GetAssocData(interp, ITCL_INTERP_DATA,
        (Tcl_InterpDeleteProc**)NULL);

    assert(info != NULL);
    oldVal = info->protection;

    if (newLevel != 0) {
        assert(newLevel == ITCL_PUBLIC ||
            newLevel == ITCL_PROTECTED ||
            newLevel == ITCL_PRIVATE ||
            newLevel == ITCL_DEFAULT_PROTECT);
        info->protection = newLevel;
    }
    return oldVal;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_ProtectionStr()
 *
 *  Converts an integer protection code (ITCL_PUBLIC, ITCL_PROTECTED,
 *  or ITCL_PRIVATE) into a human-readable character string.  Returns
 *  a pointer to this string.
 * ------------------------------------------------------------------------
 */
char*
Itcl_ProtectionStr(pLevel)
    int pLevel;     /* protection level */
{
    switch (pLevel) {
    case ITCL_PUBLIC:
        return "public";
    case ITCL_PROTECTED:
        return "protected";
    case ITCL_PRIVATE:
        return "private";
    }
    return "<bad-protection-code>";
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_CanAccess()
 *
 *  Checks to see if a class member can be accessed from a particular
 *  namespace context.  Public things can always be accessed.  Protected
 *  things can be accessed if the "from" namespace appears in the
 *  inheritance hierarchy of the class namespace.  Private things
 *  can be accessed only if the "from" namespace is the same as the
 *  class that contains them.
 *
 *  Returns 1/0 indicating true/false.
 * ------------------------------------------------------------------------
 */
int
Itcl_CanAccess(memberPtr, fromNsPtr)
    ItclMember* memberPtr;     /* class member being tested */
    Tcl_Namespace* fromNsPtr;  /* namespace requesting access */
{
    ItclClass* fromCdPtr;
    Tcl_HashEntry *entry;

    /*
     *  If the protection level is "public" or "private", then the
     *  answer is known immediately.
     */
    if (memberPtr->protection == ITCL_PUBLIC) {
        return 1;
    }
    else if (memberPtr->protection == ITCL_PRIVATE) {
        return (memberPtr->classDefn->namesp == fromNsPtr);
    }

    /*
     *  If the protection level is "protected", then check the
     *  heritage of the namespace requesting access.  If cdefnPtr
     *  is in the heritage, then access is allowed.
     */
    assert (memberPtr->protection == ITCL_PROTECTED);

    if (Itcl_IsClassNamespace(fromNsPtr)) {
        fromCdPtr = (ItclClass*)fromNsPtr->clientData;

        entry = Tcl_FindHashEntry(&fromCdPtr->heritage,
            (char*)memberPtr->classDefn);

        if (entry) {
            return 1;
        }
    }
    return 0;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_CanAccessFunc()
 *
 *  Checks to see if a member function with the specified protection
 *  level can be accessed from a particular namespace context.  This
 *  follows the same rules enforced by Itcl_CanAccess, but adds one
 *  special case:  If the function is a protected method, and if the
 *  current context is a base class that has the same method, then
 *  access is allowed.
 *
 *  Returns 1/0 indicating true/false.
 * ------------------------------------------------------------------------
 */
int
Itcl_CanAccessFunc(mfunc, fromNsPtr)
    ItclMemberFunc* mfunc;     /* member function being tested */
    Tcl_Namespace* fromNsPtr;  /* namespace requesting access */
{
    ItclClass *cdPtr, *fromCdPtr;
    ItclMemberFunc *ovlfunc;
    Tcl_HashEntry *entry;

    /*
     *  Apply the usual rules first.
     */
    if (Itcl_CanAccess(mfunc->member, fromNsPtr)) {
        return 1;
    }

    /*
     *  As a last resort, see if the namespace is really a base
     *  class of the class containing the method.  Look for a
     *  method with the same name in the base class.  If there
     *  is one, then this method overrides it, and the base class
     *  has access.
     */
    if ((mfunc->member->flags & ITCL_COMMON) == 0 &&
        Itcl_IsClassNamespace(fromNsPtr)) {

        cdPtr = mfunc->member->classDefn;
        fromCdPtr = (ItclClass*)fromNsPtr->clientData;

        if (Tcl_FindHashEntry(&cdPtr->heritage, (char*)fromCdPtr)) {
            entry = Tcl_FindHashEntry(&fromCdPtr->resolveCmds,
                mfunc->member->name);

            if (entry) {
                ovlfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry);
                if ((ovlfunc->member->flags & ITCL_COMMON) == 0 &&
                     ovlfunc->member->protection < ITCL_PRIVATE) {
                    return 1;
                }
            }
        }
    }
    return 0;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_GetTrueNamespace()
 *
 *  Returns the current namespace context.  This procedure is similar
 *  to Tcl_GetCurrentNamespace, but it supports the notion of
 *  "transparent" call frames installed by Itcl_HandleInstance.
 *
 *  Returns a pointer to the current namespace calling context.
 * ------------------------------------------------------------------------
 */
Tcl_Namespace*
Itcl_GetTrueNamespace(interp, info)
    Tcl_Interp *interp;        /* interpreter being queried */
    ItclObjectInfo *info;      /* object info associated with interp */
{
    int i, transparent;
    Itcl_CallFrame *framePtr, *transFramePtr;
    Tcl_Namespace *contextNs;

    /*
     *  See if the current call frame is on the list of transparent
     *  call frames.
     */
    transparent = 0;

    framePtr = _Tcl_GetCallFrame(interp, 0);
    for (i = Itcl_GetStackSize(&info->transparentFrames)-1; i >= 0; i--) {
        transFramePtr = (Itcl_CallFrame*)
            Itcl_GetStackValue(&info->transparentFrames, i);

        if (framePtr == transFramePtr) {
            transparent = 1;
            break;
        }
    }

    /*
     *  If this is a transparent call frame, return the namespace
     *  context one level up.
     */
    if (transparent) {
        framePtr = _Tcl_GetCallFrame(interp, 1);
        if (framePtr) {
            contextNs = framePtr->nsPtr;
        } else {
            contextNs = Tcl_GetGlobalNamespace(interp);
        }
    }
    else {
        contextNs = Tcl_GetCurrentNamespace(interp);
    }
    return contextNs;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_ParseNamespPath()
 *
 *  Parses a reference to a namespace element of the form:
 *
 *      namesp::namesp::namesp::element
 *
 *  Returns pointers to the head part ("namesp::namesp::namesp")
 *  and the tail part ("element").  If the head part is missing,
 *  a NULL pointer is returned and the rest of the string is taken
 *  as the tail.
 *
 *  Both head and tail point to locations within the given dynamic
 *  string buffer.  This buffer must be uninitialized when passed
 *  into this procedure, and it must be freed later on, when the
 *  strings are no longer needed.
 * ------------------------------------------------------------------------
 */
void
Itcl_ParseNamespPath(name, buffer, head, tail)
    CONST char *name;    /* path name to class member */
    Tcl_DString *buffer; /* dynamic string buffer (uninitialized) */
    char **head;         /* returns "namesp::namesp::namesp" part */
    char **tail;         /* returns "element" part */
{
    register char *sep, *newname;

    Tcl_DStringInit(buffer);

    /*
     *  Copy the name into the buffer and parse it.  Look
     *  backward from the end of the string to the first '::'
     *  scope qualifier.
     */
    Tcl_DStringAppend(buffer, name, -1);
    newname = Tcl_DStringValue(buffer);

    for (sep=newname; *sep != '\0'; sep++)
        ;

    while (--sep > newname) {
        if (*sep == ':' && *(sep-1) == ':') {
            break;
        }
    }

    /*
     *  Found head/tail parts.  If there are extra :'s, keep backing
     *  up until the head is found.  This supports the Tcl namespace
     *  behavior, which allows names like "foo:::bar".
     */
    if (sep > newname) {
        *tail = sep+1;
        while (sep > newname && *(sep-1) == ':') {
            sep--;
        }
        *sep = '\0';
        *head = newname;
    }

    /*
     *  No :: separators--the whole name is treated as a tail.
     */
    else {
        *tail = newname;
        *head = NULL;
    }
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_DecodeScopedCommand()
 *
 *  Decodes a scoped command of the form:
 *
 *      namespace inscope <namesp> <command>
 *
 *  If the given string is not a scoped value, this procedure does
 *  nothing and returns TCL_OK.  If the string is a scoped value,
 *  then it is decoded, and the namespace, and the simple command
 *  string are returned as arguments; the simple command should
 *  be freed when no longer in use.  If anything goes wrong, this
 *  procedure returns TCL_ERROR, along with an error message in
 *  the interpreter.
 * ------------------------------------------------------------------------
 */
int
Itcl_DecodeScopedCommand(interp, name, rNsPtr, rCmdPtr)
    Tcl_Interp *interp;		/* current interpreter */
    CONST char *name;		/* string to be decoded */
    Tcl_Namespace **rNsPtr;	/* returns: namespace for scoped value */
    char **rCmdPtr;		/* returns: simple command word */
{
    Tcl_Namespace *nsPtr = NULL;
    char *cmdName;
    int len = strlen(name);
    CONST char *pos;
    int listc, result;
    CONST char **listv;

    cmdName = ckalloc((unsigned)strlen(name)+1);
    strcpy(cmdName, name);

    if ((*name == 'n') && (len > 17) && (strncmp(name, "namespace", 9) == 0)) {
	for (pos = (name + 9);  (*pos == ' ');  pos++) {
	    /* empty body: skip over spaces */
	}
	if ((*pos == 'i') && ((pos + 7) <= (name + len))
	        && (strncmp(pos, "inscope", 7) == 0)) {

            result = Tcl_SplitList(interp, name, &listc,
		    &listv);
            if (result == TCL_OK) {
                if (listc != 4) {
                    Tcl_AppendResult(interp,
                        "malformed command \"", name, "\": should be \"",
                        "namespace inscope namesp command\"",
                        (char*)NULL);
                    result = TCL_ERROR;
                } else {
                    nsPtr = Tcl_FindNamespace(interp, listv[2],
                        (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG);

                    if (!nsPtr) {
                        result = TCL_ERROR;
                    } else {
			ckfree(cmdName);
                        cmdName = ckalloc((unsigned)(strlen(listv[3])+1));
                        strcpy(cmdName, listv[3]);
                    }
                }
            }
            ckfree((char*)listv);

            if (result != TCL_OK) {
                char msg[512];
                sprintf(msg, "\n    (while decoding scoped command \"%.400s\")", name);
                Tcl_AddObjErrorInfo(interp, msg, -1);
                return TCL_ERROR;
            }
	}
    }

    *rNsPtr = nsPtr;
    *rCmdPtr = cmdName;
    return TCL_OK;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_EvalArgs()
 *
 *  This procedure invokes a list of (objc,objv) arguments as a
 *  single command.  It is similar to Tcl_EvalObj, but it doesn't
 *  do any parsing or compilation.  It simply treats the first
 *  argument as a command and invokes that command in the current
 *  context.
 *
 *  Returns TCL_OK if successful.  Otherwise, this procedure returns
 *  TCL_ERROR along with an error message in the interpreter.
 * ------------------------------------------------------------------------
 */
int
Itcl_EvalArgs(interp, objc, objv)
    Tcl_Interp *interp;      /* current interpreter */
    int objc;                /* number of arguments */
    Tcl_Obj *CONST objv[];   /* argument objects */
{
    int result;
    Tcl_Command cmd;
    Command *cmdPtr;
    int cmdlinec;
    Tcl_Obj **cmdlinev;
    Tcl_Obj *cmdlinePtr = NULL;

    /*
     * Resolve the command by converting it to a CmdName object.
     * This caches a pointer to the Command structure for the
     * command, so if we need it again, it's ready to use.
     */
    cmd = Tcl_GetCommandFromObj(interp, objv[0]);
    cmdPtr = (Command*)cmd;

    cmdlinec = objc;
    cmdlinev = (Tcl_Obj	**) objv;

    /*
     * If the command is still not found, handle it with the
     * "unknown" proc.
     */
    if (cmdPtr == NULL) {
        cmd = Tcl_FindCommand(interp, "unknown",
            (Tcl_Namespace *) NULL, /*flags*/ TCL_GLOBAL_ONLY);

        if (cmd == NULL) {
            Tcl_ResetResult(interp);
            Tcl_AppendResult(interp,
                "invalid command name \"",
                Tcl_GetStringFromObj(objv[0], NULL), "\"", NULL);
            return TCL_ERROR;
        }
        cmdPtr = (Command*)cmd;

        cmdlinePtr = Itcl_CreateArgs(interp, "unknown", objc, objv);
        Tcl_ListObjGetElements(NULL, cmdlinePtr, &cmdlinec, &cmdlinev);
    }

    /*
     *  Finally, invoke the command's Tcl_ObjCmdProc.  Be careful
     *  to pass in the proper client data.
     */
    Tcl_ResetResult(interp);
    result = (*cmdPtr->objProc)(cmdPtr->objClientData, interp,
        cmdlinec, cmdlinev);

    if (cmdlinePtr) {
        Tcl_DecrRefCount(cmdlinePtr);
    }
    return result;
}


/*
 * ------------------------------------------------------------------------
 *  Itcl_CreateArgs()
 *
 *  This procedure takes a string and a list of (objc,objv) arguments,
 *  and glues them together in a single list.  This is useful when
 *  a command word needs to be prepended or substituted into a command
 *  line before it is executed.  The arguments are returned in a single
 *  list object, and they can be retrieved by calling
 *  Tcl_ListObjGetElements.  When the arguments are no longer needed,
 *  they should be discarded by decrementing the reference count for
 *  the list object.
 *
 *  Returns a pointer to the list object containing the arguments.
 * ------------------------------------------------------------------------
 */
Tcl_Obj*
Itcl_CreateArgs(interp, string, objc, objv)
    Tcl_Interp *interp;      /* current interpreter */
    CONST char *string;      /* first command word */
    int objc;                /* number of arguments */
    Tcl_Obj *CONST objv[];   /* argument objects */
{
    int i;
    Tcl_Obj *listPtr;

    listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL);
    Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr,
        Tcl_NewStringObj(string, -1));

    for (i=0; i < objc; i++) {
        Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, objv[i]);
    }

    Tcl_IncrRefCount(listPtr);
    return listPtr;
}
