summaryrefslogtreecommitdiff
path: root/itcl/itcl/generic/itcl_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'itcl/itcl/generic/itcl_util.c')
-rw-r--r--itcl/itcl/generic/itcl_util.c1383
1 files changed, 1383 insertions, 0 deletions
diff --git a/itcl/itcl/generic/itcl_util.c b/itcl/itcl/generic/itcl_util.c
new file mode 100644
index 00000000000..e33823ba877
--- /dev/null
+++ b/itcl/itcl/generic/itcl_util.c
@@ -0,0 +1,1383 @@
+/*
+ * ------------------------------------------------------------------------
+ * 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
+ *
+ * RCS: $Id$
+ * ========================================================================
+ * 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"
+#include "tclCompile.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;
+
+
+/*
+ * 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.
+ * ------------------------------------------------------------------------
+ */
+#ifndef NDEBUG
+
+void
+Itcl_Assert(testExpr, fileName, lineNumber)
+ char *testExpr; /* string representing test expression */
+ char *fileName; /* file name containing this call */
+ int lineNumber; /* line number containing this call */
+{
+ fprintf(stderr, "Assertion failed: \"%s\" (line %d of %s)",
+ testExpr, lineNumber, fileName);
+ abort();
+}
+
+#endif
+
+
+/*
+ * ------------------------------------------------------------------------
+ * 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 */
+{
+ Itcl_List *listPtr = elemPtr->owner;
+ assert(listPtr->validate == ITCL_VALID_LIST);
+ assert(elemPtr != NULL);
+
+ 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.
+ */
+ 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 */
+
+ (*chunk->fproc)((char*)chunk->data);
+ Tcl_DeleteHashEntry(entry);
+ ckfree((char*)chunk);
+ }
+}
+
+/*
+ * ------------------------------------------------------------------------
+ * 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.
+ */
+ 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++;
+ }
+}
+
+/*
+ * ------------------------------------------------------------------------
+ * 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;
+ if (ItclPreservedList) {
+ entry = Tcl_FindHashEntry(ItclPreservedList,(char*)cdata);
+ }
+ if (!entry) {
+ panic("Itcl_ReleaseData can't find reference for 0x%x", 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 */
+ (*chunk->fproc)((char*)chunk->data);
+ }
+
+ Tcl_DeleteHashEntry(entry);
+ ckfree((char*)chunk);
+ }
+}
+
+
+/*
+ * ------------------------------------------------------------------------
+ * 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;
+ char *val;
+
+ 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.
+ */
+ if ((iPtr->flags & ERR_IN_PROGRESS) != 0) {
+ 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 */
+{
+ Interp *iPtr = (Interp*)interp;
+ InterpState *info = (InterpState*)state;
+ int status;
+
+ if (info->validate != TCL_STATE_VALID) {
+ 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) {
+ (void) Tcl_SetVar2(interp, "errorCode", (char*)NULL,
+ info->errorCode, TCL_GLOBAL_ONLY);
+ iPtr->flags |= ERROR_CODE_SET;
+
+ 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;
+
+ if (info->validate != TCL_STATE_VALID) {
+ 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;
+ Tcl_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 = (Tcl_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)
+ 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;
+
+ 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);
+ name = Tcl_DStringValue(buffer);
+
+ for (sep=name; *sep != '\0'; sep++)
+ ;
+
+ while (--sep > name) {
+ 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 > name) {
+ *tail = sep+1;
+ while (sep > name && *(sep-1) == ':') {
+ sep--;
+ }
+ *sep = '\0';
+ *head = name;
+ }
+
+ /*
+ * No :: separators--the whole name is treated as a tail.
+ */
+ else {
+ *tail = name;
+ *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 */
+ 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 = name;
+ int len = strlen(name);
+
+ char *pos;
+ int listc, result;
+ char **listv;
+
+ 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_AppendStringsToObj(Tcl_GetObjResult(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 {
+ 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_AppendStringsToObj(Tcl_GetObjResult(interp),
+ "invalid command name \"",
+ Tcl_GetStringFromObj(objv[0], (int*)NULL), "\"",
+ (char*)NULL);
+ return TCL_ERROR;
+ }
+ cmdPtr = (Command*)cmd;
+
+ cmdlinePtr = Itcl_CreateArgs(interp, "unknown", objc, objv);
+
+ (void) Tcl_ListObjGetElements((Tcl_Interp*)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 */
+ 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;
+}